본 글은 OAuth2.0에 대해 설명하며, 인증, 인가, JWT를 배경지식으로 요구합니다. 또한 OAuth2.0을 들어 보았고 관련 글이나 영상을 본 사람을 대상으로 합니다. 실습이나 예제는 없습니다.
인증, 인가, 로그인 등을 검색하면 소셜 로그인이라는 키워드와 함께 OAuth2.0을 자주 마주친다. 여태까지 OAuth2.0 == 소셜 로그인
으로 알고 있었다. 하지만 소셜 로그인을 위해 OAuth2.0를 사용할 수 있는 것이지 OAuth2.0 == 소셜 로그인
이 아니다. 소셜 로그인과 상관없이 OAuth2.0을 사용할 수 있다.
Okta에 의하면 OAuth2.0은 인가 프로토콜이며, 인증 프로토콜이 아니다.
인가를 위해 액세스 토큰을 사용하며, 그 중 JWT가 많이 사용된다.
즉 OAuth2.0은 인가를 위해 사용하는 것이며, 하나의 예시가 소셜 로그인일 뿐 다른 방식으로 다양하게 사용할 수 있다.
OAuth2.0의 동작 방식을 알기 전에 알아야 할 사전 지식이 있다. 아래는 OAuth2.0 맥락 아래 사용하는 용어들이다.
리소스 소유자(Resource Owner): 보호된 리소스를 소유하고 해당 리소스에 대한 접근 권한을 가지는 사용자 또는 시스템. 사용자라고 생각하면 편하다.
클라이언트(Client): OAuth 2.0을 사용하여 보호된 리소스에 접근하는 시스템. 리소스 소유자가 직접 상호작용을 하는 애플리케이션이다. 웹 애플리케이션, 모바일 앱 또는 백엔드 서버가 그 예시로 리소스 서버에 접근하기 위해 액세스 토큰을 사용한다.
인가 서버(Authorization Server): 클라이언트로부터 액세스 토큰 요청을 받고, 리소스 소유자의 동의를 거친 후 토큰을 발급하는 서버. 이 과정에서 리소스 소유자의 인증은 별도로 이루어지며, 인가 서버는 이미 인증된 사용자에 대해 인가를 수행한다. 예를 들어, 사용자가 카카오 로그인을 통해 다른 사이트에 회원 가입을 하거나 사용하는 경우, 인증은 카카오 로그인을 통해 이루어지고, 인가는 카카오 인가 서버에서 처리된다.
리소스 서버(Resource Server): 실제 리소스가 있고 클라이언트로부터 액세스 요청을 받는 서버이다. 소셜 로그인으로 생각해보면 구글 메일, 구글 캘린더 등 리소스가 있는 서버를 예시로 볼 수 있다.
OAuth2.0은 다양한 방식을 사용하여 리소스 서버에 대한 접근 권한을 부여하고 관리할 수 있다. 이 중 Authorization Code Grant
와 Client Credentials Grant
그리고 Resource owner password credentials Grant
를 알아보자. 더 다양한 인가 유형은 맨 마지막 출처를 보면 확인할 수 있다.
사진을 처음 보면 조금 복잡하지만, 구글이 아닌 사이트(Client 이하 A 서버)에서 구글 로그인을 하는 상황을 떠올리면서 보면 이해가 쉽다.
Enter URL : 유저가 A에 구글 로그인을 요청한다.
Open URL : 브라우저가 해당 요청 URL을 서버로 보낸다.
Redirect to AuthZ Server : A 서버는 AuthZ URL로 redirect 응답을 한다. A 서버로 나의 구글 아디이와 비밀번호가 넘어가는 것은 너무 위험하니 A서버가 아닌 구글 서버에서 로그인 처리하려는 목적이다.
Opens redicrect URL : A 서버에게 받은 redirect URL로 다시 요청을 보낸다.
Present Authorization UI: 브라우저는 AuthorZ 서버에서 제공하는 로그인 UI를 사용자에게 보여준다.
Present credentials and authorize or deny: 사용자가 자격 증명(아이디와 비밀번호)을 입력하고 로그인 여부를 선택한다.
Present submitted data from user: 브라우저가 사용자가 입력한 데이터를 A 서버가 아닌 AuthZ 서버에 직접 제출한다.
Verify and create Authorization code: AuthZ 서버는 사용자의 자격 증명을 확인하고 증명이 되는 경우 Authorization code를 생성한다.
Redirect to Web Server with Authorization Code: AuthZ 서버는 Authorization Code와 redirect URL(A 서버의 URL)을 브라우저에 제공한다.
Follow redirect to Web Server: 브라우저는 Authorization Code를 포함한 URL로 A 서버에 다시 요청을 보낸다.
Present Authorization Code: A 서버는 받은 Authorization Code를 AuthZ 서버에 제출한다.
Return Access Token: AuthZ 서버는 Authorization Code를 확인하고 유효하면 Access Token을 A 서버에 반환한다.
Call protected resource with Access Token: A 서버는 받은 Access Token을 이용하여 Resource 서버에 보호된 자원을 요청한다.
Return protected resource: Resource 서버는 Access Token을 확인하고 유효하면 요청된 자원을 A 서버에 반환한다.
브라우저가 7번 순서인 Present submitted data from user
을 처리하는 url 예시
curl -X GET https://authorization-server.example.com/oauth/authorize
?response_type=code
&client_id=your_client_id
&redirect_uri=A_서버_redirect_uri
&state=your_state
&scope=your_scope
A 서버가 12번 순서인 Return Access Token
을 처리하는 url 예시
curl -X POST "https://authorization-server.example.com/token" \
-d "grant_type=authorization_code" \
-d "code=your_authorization_code" \
-d "client_id=your_client_id" \
-d "client_secret=your_client_secret" \
-d "redirect_uri=your_redirect_uri"
유저 개입 없이 클라이언트의 자격 증명을 사용하여 리소스에 접근할 하는 방식이다. 상황 예시로는 서버대 서버간 통신을 하거나 주기적인 작업 혹은 자동화 작업이 필요할 때 등이 있다.
Client credentials: 클라이언트(예: 서버)는 AuthorZ 서버로 클라이언트 자격 증명을 전송한다.
Authenticate Client: AuthorZ 서버는 클라이언트 자격 증명을 확인하여 클라이언트를 인증하고 자격 증명이 올바르면 클라이언트에게 Access Token을 발급한다.
Access token with NO refresh token: AuthorZ 서버는 Access Token을 Refresh Token없이 클라이언트에게 반환한다.
Access protected resource with access token: 클라이언트는 받은 Access Token을 사용하여 Resource 서버에 자원을 요청한다.
Protected resource response: Resource 서버는 Access Token을 확인하고 유효하다면 보호된 자원을 클라이언트에게 반환한다.
유저의 자격 증명(아이디, 비밀번호 등)을 사용하여 클라이언트가 토큰을 발급 받고 리소스에 접근하는 방식이다. 로그인을 직접 구현하는 상황을 생각하면 된다. 크게 추천할 만한 방식은 아니지만 Client, AuthorZ 서버, Resource 서버를 구분 안 하고 하나의 백엔드에서 구현해도 되기는 한다.
Resource Owner's credentials: 유저가 A 서버에 자신의 자격 증명(아이디와 비밀번호)을 전송한다.
Resource Owner's credentials: A 서버는 유저의 자격 증명을 AuthorZ 서버로 전송한다.
Authenticate Resource Owner: AuthorZ 서버는 유저의 자격 증명을 확인한다.
Authenticate Client: 3번 과정과 함께 AuthorZ 서버는 A 서버의 자격 증명도 확인한다. (보안상 A 서버가 아닌 제 3의 서버에서 요청을 막기 위해)
Access token with optional refresh token: 인증이 성공하면 AuthorZ 서버는 Access Token을 A 서버에 반환한다. 이때 Refresh Token도 선택적으로 포함될 수 있다.
Access protected resource with access token: A 서버는 받은 Access Token을 사용하여 Resource 서버에 자원을 요청한다.
Protected resource response: Resource 서버는 Access Token을 확인하고 유효하다면 자원을 A서버에 반환한다.
Client, AuthorZ 서버, Resource 서버를 통합해 로그인을 구현한 추상적인 Fastapi
예시.
@app.post("/token", response_model=Token)
async def login_for_access_token(
username: str = Form(), password: str = Form()
):
try:
user = authenticate_user(username, password) #인증
except AuthorizationError:
raise AuthorizationError
login_token = make_login_token(user)
return login_token #인가 토큰 발급
# get_current_user 인가 토큰 검증 함수
@app.get("/resource", response_model=Resource) #인가 토큰으로 리소스 접근
async def read_resource(current_user: User = Depends(get_current_user)):
if current_user is None:
raise TokenError
resource = get_resource(current_user)
return resource #리소스 반납
OAuth2.0을 직접 사용하거나 사용한 코드를 본 적이 없었다. OAuth2.0에 관해 설명해 주세요
라는 기술 인터뷰 예상 질문을 어디선가 보고 유투브 영상과 블로그 글 몇 개를 읽으며 OAuth2.0 == 소셜 로그인
이며 OAuth2.0
을 알고 있다고 착각했었다. 최근 프로젝트에서 OAuth2.0
을 사용하는데 OAuth2.0 == 소셜 로그인
이라는 관점에서만 코드를 보니 전혀 이해가 안 되어서 다시 공부하는 계기가 되었다. 처음부터 제대로 공부하는 사람이 되어야지...
긴 글 읽어주셔서 감사합니다 :)