[FastAPI] 7. Google-auth 및 PyJWT를 이용한 OAuth2 인증 구현 2

반응형

지난 글에 이어서 이번 포스트에서는 OAuth2 인증 토큰을 API에서 받아 처리하는 방법에 대해 알아보도록 하겠습니다.

 

 

 

HTTP Header

우리가 REST API를 통신하는 데 사용하는 HTTP 프로토콜은 Header와 Body라는 구조로 이뤄져 있습니다. Header와 Body 모두 개발자가 다룰 수 있는 데이터 구조로 되어 있으며 Header에는 전송하고자 하는 서버 혹은 클라이언트의 정보 내지 전송하고자 하는 대상에 부가적인 메타데이터를 넘겨줄 떄 사용합니다.

 

쉬운 예시로 위와 같이 로컬 서버에게 GET 메소드를 이용해서 리소스를 가져오는 호출을 전달했을 경우 데이터를 받는 대상자에게 부가적인 메타 데이터를 가져올 수 있도록 Key-value 형태의 데이터 구조를 가지고 있습니다.

 

이 중에서 가장 많이 들어가는 Header 값에는 Content-type, Cache-Control 등이 있는데, Content-type은 서버가 클라이언트 혹은 클라이언트가 서버에게 주는 데이터의 유형, Cache-control은 REST에서 지원하는 캐시를 어느 시점까지 보관하고 만료할 것인지 등에 대한 정보가 담겨지게 됩니다.

 

여기서 우리는 헤더에 암호화 내지 인코딩된 JWT 토큰을 담을 건데요. 왜 이것을 body에 담지 않고, header에 담는 것일까요?

우리는 Header와 Body에 대한 명확한 구분이 필요한데, body는 해당 서비스에서 가져오거나 담는 리소스를 담을 때 사용하고, Header는 서버의 정보나 인증 수단과 같은 서비스에 필요한 리소스 외의 항목을 담을 때 사용하기로 구분되어졌기 때문인데요. 

 

따라서 엔드포인트의 리소스가 어떤 것이 중심이냐에 따라 이 데이터가 Header에 들어가고, Body에 들어가는지는 서비스 개발자가 정할 수 있는데, 보통은 이런식으로 나누는 것이 평범하다고 보시면 좋을 것 같습니다.

 

 

 

 

Get Header in FastAPI

그렇다면 FastAPI에서 클라이언트가 요청한 Header 값을 어떤식으로 받을 수 있을까요?

 

일반적으로 FastAPI에서 클라이언트의 요청값을 받을 때는 위의 코드로 받을 수 있습니다. 기존의 pydantic과 같은 Schema validation을 사용했을 때와는 사뭇 다른 모습이지만 일반적으로는 위와 같이 받으며 Request 객체를 통해서 header, cookie, path_params 등을 종합적으로 확인할 수 있습니다.

 

만약 특정 Key에 해당하는 Header 값만을 받고자 하는 경우 FastAPI의 모듈에서 Header 함수를 사용하면 특정 Key 값에 해당하는 Header 값을 받아올 수 있습니다.

 

우리는 여기서 Authorization 이라는 Key를 사용할 것이며 이쪽에 JWT 토큰을 넣어서 보내면 이를 decode해서 사용자의 정보를 보여주는 API를 만들어보도록 하겠습니다.

 

 

 

 

Decode Token 

지난 포스트에서 토큰을 인코딩했을 때 payload와 key를 파라미터로 사용하여 토큰을 암호화 했었는데요. 그런데, 여기에는 약간의 함정이 숨겨져 있습니다.

 

encode를 했을 때와 마찬가지로 decode에 key 값과 토큰 값을 사용하게 되면 decode 되지 않고 오류가 나타납니다.

 

이 오류는 algorithms 파라미터를 반드시 추가하라는 내용인데요. PyJWT 1.7.1 버전을 사용했다면 아래의 코드를 이용하여 이 과정을 무시할 수도 있습니다.

 

그러나 PyJWT 2.x 버전 이상에서는 위 코드를 사용할 수 없으며 반드시 algorithms를 지정해줘야만 합니다. 그렇다면 우리는 어떤 알고리즘을 사용한 것일까요?

 

encode 함수를 조금 살펴보면 기본 알고리즘으로 HS256을 사용했음을 알 수 있습니다. 따라서 decode를 할 때 algorihtms에 HS256을 붙여주면 decode가 됩니다.

 

만약 토큰을 입력받지 못한 경우 401 오류를 반환하도록 합니다.

 

 

 

 

Use DI

여기까지 인증이 필요한 구간에 대해서 Authorization 헤더에 있는 토큰을 받고 이 토큰이 주어짐에 따라 반환하는 값을 바꾸도록 구현하였습니다.

 

그런데, 인증이 필요한 API 구간마다 이러한 코드를 쓰게 된다면 중복 코드가 빈번하게 발생하게 됩니다. 우리는 여기서 Middleware를 사용하여 API 호출 전후구간에 인증 절차를 추가하여 사용할 수도 있습니다. 그렇지만 필요없는 API 구간에서 이를 실행하는 것은 불필요한 호출에 해당하기도 합니다.

 

이럴 떄는 FastAPI의 DI(Dependency Injection) 기법을 사용할 수 있습니다. FastAPI에서는 함수와 클래스 모두에 의존성 주입을 걸을 수 있는데, 간단한 사용법의 예시는 이미 SQLAlchemy를 FastAPI에서 적용하는 포스트에서 다뤄봤습니다.

 

따라서 이 기법을 가지고 필요한 API 구간에 Token 인증하는 요소를 넣어보도록 하겠습니다.

 

클래스 호출시 Header에서 authorization 값을 받도록 합니다. 만약 값이 없다면 401 Unauthorized 오류를 반환합니다.

 

FastAPI에서 제공하는 Depends 함수를 이용하여 AuthProvider를 주입하면 API가 호출될 때마다 AuthProvider 클래스를 생성하는데, 위에서 클래스 생성시 호출되는 __call__ 메소드를 이용하여 authorization 값을 반환했으므로 이에 authorization 값이 반환되는 것을 볼 수 있습니다.

 

 

 

 

Using DI with OAuth2Scheme

FastAPI에서는 이와 비슷한 형태로 OAuth2Scheme 클래스를 제공하고 있습니다. Bearer 토큰 기반의 OAuth2PasswordBearer을 제공하며 우리는 Bearer 토큰 포맷형태로 JWT 토큰을 인코딩 하였으므로 이 클래스를 이용하여 좀 더 쉽게 OAuth2 기반의 인증 서비스를 만들 수 있습니다.

 

여기서 tokenUrl 파라미터를 받는데, 이것은 OAuthFlows을 사용하여 API 문서에서 Flow를 확인하기 위함입니다. 로그인 URI가 있다면 적고 그렇지 않으면 적지 않으셔도 됩니다.

 

auto_error 파라미터는 토큰이 비정상적이거나 주어지지 않은 경우 자동으로 오류 반환을 할 것인지를 묻는 파라미터인데, 기본값은 True 입니다.

 

이런식으로 필요한 API 포인트에 DI를 사용하여 쉽게 JWT 토큰을 디코딩하여 권한을 확인하고 인증할 수 있습니다.

 

 

 

 

마치며...

FastAPI로 OAuth2 로그인을 이용하여 인증하는 방법에 대해 간단히 다뤄봤습니다. Spring Security와 비교하면 러닝 커브는 그다지 많이 깊지 않은 느낌입니다. 그러나 JWT가 어떻게 동작하는지 알고, 이를 어떤식으로 구현하는 것에 대해 익숙해져 있어야 합니다.

 

반대로 Spring Security는 JWT의 구동 방식 보다는 Spring Security의 레이어나 콜백 함수 등을 알고 이를 적절히 이용하는 방법이기 때문에 어떻게 보면 러닝 커브는 Spring Security가 좀 더 높다고 볼 수 있을 것 같네요.

 

 

 

반응형

Tistory Comments 0