Web
수정·

OAuth2 와 OpenID Connect

by Dohoon Kim · 23년 10월 28일 22:00:57

OAuth2.0 그리고 OIDC

OAuth2.0 Framework는 인가를 위한 Framework로 RFC6749 에 정의되어있다. 일상에서는 소셜 로그인하기 등의 기능으로 접하고 있지만 실제적으로는 인가를 위한 프레임워크이다. 이는 문서에서도 OAuth2가 Authorization Framework 라고 명시되어 있는 것으로 알 수 있다. 인가가 주 목적이라는 것은 제한된 Resource 대한 접근을 통제하기 위한 것이라는 것이다.

예를 들어 우리가 어떤 어플리케이션을 개발하는데 사용자의 구글 드라이브에 파일을 저장하고 싶다고 가정해보자. 우리는 사용자 구글드라이브에 파일을 조회, 등록, 수정, 삭제를 하기 위해 사용자로부터 Google의 Email 주소 및 Password를 입력받아 로그인을 한 뒤 API를 이용할 수 있다. 하지만 이렇게되면 보안 상의 위험이 크다. OAuth2.0 은 이런 상황에서 Google, Naver, Facebook 등 거대 서비스 측에서 사용자 Resource를 접근할 수 있도록 고안된 Framework라고 할 수 있다. OAuth2.0 인가 서버를 제공하는 Google, Naver, Facebook 과 같은 업체 OAuth2.0 Provider(이하 Provider)라고 하며 이를 이용하여 해당 플랫폼에 가입된 사용자의 정보등을 3rd Party Application에서 이용할 수 있다.

Terms

우선 용어를 정리하자면 아래와 같다.

Resource Owner: 3rd Party Application(이하 Client) 를 이용하는 실제 사용자로, Provider에 사용자 정보가 등록되어 있다. 이하 사용자로 부른다.
Client: 3rd Party Application으로 Provider로부터 사용자 정보를 획득하여 이용하는 주체이다.
Authorization Server: 인가 절차를 수행하는 서버로 사용자가 인증 절차를 성공했을 경우 Authorization code, Access token등을 발급한다.
Resource Server: Protected Resource를 제공하는 서버

여기서 Authorization Server와 Resource Server는 함께 묶인 경우도 있고 따고 구현된 경우도 있다.

기본 동작 방식

 +--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

RFC6749 에 명시된 기본 동작 방식은 위와 같다. 여기서 핵심은 클라이언트가 C,D 과정을 통해 Access token을 발급받고 Access token을 이용하여 Resource Server의 Resource를 획득한다는 것이다. 인증 과정은 이 Access Token을 발급받기 위한 일부에 지나지 않는다.

OAuth2 Provider 이용 사전 준비 사항

OAuth2 Provider 측에 Application 을 등록해야한다. 등록을 하면 cliend_id, client_secret이 발급되며, 등록 시 redirect_uri와 origin을 등록해야한다.

Authorization Grant

OAuth2.0 동작 방식의 기본이며 가장 권장되는 방식이다. Server Side Web Application에서 이용되며, Javascript를 이용한 Client Side Web Application에서도 별도의 Backend를 구성한다면 이용 가능하다. Mobile Application의 경우 PKCE라는 확장을 이용하는데 Authorization Grant 방식에 client_secret이 일회용 코드로 변경된 정도이다.

imageURL

동작 순서는 아래와 같다.

  1. 사용자는 클라이언트에게 OAuth2 요청을 한다.
  2. 클라이언트는 OAuth2 Provider 인증 페이지로 리디렉션한다.
  3. 사용자는 인증페이지에서 인증 절차를 수행한다.
  4. Authorization Server는 사용자 인증이 성공하면 Client의 특정 redirect_uri 로 리디렉션하며, 이 때 query parameter로 authorization_code를 포함시킨다.
  5. Client는 Authorization code를 이용하여 Access token 발급 요청을 한다.
  6. Authorization Server는 code를 확인하여 Access token을 발급한다.
  7. Client는 발급받은 Access token을 이용하여 Resource Server에 Resource를 요청한다.
  8. Resource Server는 Access token을 통해 권한을 확인하여 Resource를 제공한다.

위 그림에서 4번 redirect_uri 를 통해 인증 절차 완료 후 3rd Party Application으로 리디렉션 되기 때문에 요청 시 query parameter로 redirect_uri 를 제공해야 하며, Authorization Server는 redirect_uri와 client_id 등을 확인하여 정상적인 접근인지 확인을 하게 된다

구글 OAuth2.0를 이용한 소셜로그인을 예시로 들어보자

  1. 사용자는 www.dohoon-kim.kr 의 로그인 페이지에서 Google 로그인 하기를 선택한다.
  2. www.dohoon-kim.kr 은 구글 로그인 페이지로 리디렉션을 한다.
GET /o/oauth2/v2/auth
HOST https://accounts.google.com
{
    scope=email&
    access_type=offline&
    include_granted_scopes=true&
    response_type=code&
    state=state_parameter_passthrough_value&
    redirect_uri=https://www.dohoon-kim.kr/oauth2/callback/google&
    client_id=client_id
}

여기서 access_type, include_granted_scopes, state 등은 보안 정책이기 때문에 무시하고, scope는 접근할 사용자 정보의 명시 redirect_uri는 인증 성공 시 Authorization code과 함께 리디렉션 시킬 uri client_id 는 어플리케이션 등록 후 발급 받은 client_id 이다.

이 페이지를 통해 사용자가 로그인을 완료하게 되면 아래와 같은 URL로 리디렉션 된다. 여기서 response_type 은 code로 명시해야 Authorization code가 발급된다.

https://www.dohoon-kim.kr/oauth2/callback/google?code=<authorization_code>

이 후 Client는 Authorization code를 이용 Access token 발급 요청을 하게 된다.

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded
{
    code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
    client_id=your_client_id&
    client_secret=your_client_secret&
    redirect_uri=https://www.dohoon-kim.kr/oauth2/callback/google&
    grant_type=authorization_code
}

위 요청이 성공하면 Access Token이 발급되는데 응답 Body는 아래와 같다.

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "token_type": "Bearer",
  "scope": "email",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

이제 Client는 이 Access token을 이용하여 서비스에 필요한 사용자 정보를 획득한다.

Implicit Grant

Implicit Grant 방식은 Token을 안전하게 저장할 수 없는 Javascript SPA 등에서 이용하기 위해 나온 방식이지만 RFC6749 문서에는 권장하지 않는 방식이며 Legacy로 표기되어 있다. Authorization Grant 방식에서 Authentication -> Authorization code -> Access Token 획득으로 이어지는 Flow를 Authentication -> Access Token으로 직접 발급 받게 간소화 되어있다. 보안 상 취약하기 때문에 Access Token 과 Refresh Token이 짧게 지정되어 있다.

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier     +---------------+
 |         -+----(A)-- & Redirection URI --->|               |
 |  User-   |                                | Authorization |
 |  Agent  -|----(B)-- User authenticates -->|     Server    |
 |          |                                |               |
 |          |<---(C)--- Redirection URI ----<|               |
 |          |          with Access Token     +---------------+
 |          |            in Fragment
 |          |                                +---------------+
 |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
 |          |          without Fragment      |     Client    |
 |          |                                |    Resource   |
 |     (F)  |<---(E)------- Script ---------<|               |
 |          |                                +---------------+
 +-|--------+
   |    |
  (A)  (G) Access Token
   |    |
   ^    v
 +---------+
 |         |
 |  Client |
 |         |
 +---------+

이 외의 방식

Resource Owner Password Credentials과 Client Credentials 방식이 있는데 전자의 경우 사용자의 계정과 Password를 직접 입력 받아 Access token을 발급하는 방식인데, 당연하게도 안쓰인다. 쓰는곳을 본적이 없다. 후자는 Server 대 Server 통신에서 이용되는데 사용자 소유의 데이터가 아닌 Resource이지만 Provider 측에서 이 Resource에 대한 접근을 Client 에 대해 허용하는 경우 client_id, client_secret 을 통해 인가를 수행한다.

Web Application 에서 OAuth 2.0을 이용한 Resource 접근

Authorization Grant 그림에서 Client 부분을 Frontend Client와 Backend Client로 구분할 수 있다. Frontend Client에서 Google로 로그인하기 같은 버튼을 선택하면 Authorization Server의 인증 페이지로 리디렉션 되고 인증 성공 시 Backend Client의 Endpoint로 Authorization code를 전달하고 Backend Client에서 Access token을 발급받아 Resource에 접근한다던가 하는 식이다.

아래는 구현 예시 들이다.

imageURL

이 그림에서는 Authorization Server에서 Frontend Client 로 리디렉션을 하는 경우이다.

  1. 사용자가 소셜 로그인 요청을 한다.
  2. Frontend Client는 페이지를 Provider의 인증 페이지로 리디렉션 한다.
  3. 사용자는 인증 절차를 수행한다.
  4. Authorization Server는 Authorization code를 포함하여 Frontend Client의 Endpoint로 리디렉션 한다.
  5. Frontend Client 는 Authorization code를 추출하여 Backend Client 에 전달한다.
  6. Backend Client는 Authorization code를 이용하여 Authorization Server에 Access token을 요청한다.
  7. Authorization Server는 Backend Client에 Access token을 발급한다.
  8. Backend Client는 Access token을 이용하여 Resource Server에 Resource를 요청한다.
  9. Resource 서버는 Access token을 확인하여 Resource에 대한 접근을 인허가 한다.

이 방식은 Authorization Server의 Redirection 이 Frontend Client로 오기 때문에 Frontend Client의 Endpoint가 필요하며, Backend Client로 REST API를 날려 Authorization code를 전달해야한다. 이 후 Backend Client는 Resource 획득 이후 로직을 처리한 뒤 Frontend Client에 응답을 반환하고 Frontend Client에서는 해당 응답을 전달받아 페이지를 갱신해야 한다.

imageURL

이 방식은 Authorization code의 redirect_uri 를 Backend Client 의 Endpoint로 설정하는 방식이다. Spring Security 에서 제공하는 OAuth2 Login의 기본 방식고 유사하다. 차이점이라고 하면 Frontend Client에서 직접 Provider의 인증 페이지로 리디렉션을 하는것이 아니고 Frontend Client가 Backend Client에 사용자의 OAuth2 인증 요청을 전달하면 Backend Client에서 리디렉션 응답을 주는것 정도이다.

  1. 사용자가 소셜 로그인 요청을 한다.
  2. Frontend Client는 페이지를 Provider의 인증 페이지로 리디렉션 한다.
  3. 사용자는 인증 절차를 수행한다.
  4. Authorization Server는 Authorization code를 포함하여 Backend Client의 Endpoint로 리디렉션 한다.
  5. Backend Client는 Authorization code를 이용하여 Authorization Server에 Access token을 요청한다.
  6. Authorization Server는 Backend Client에 Access token을 발급한다.
  7. Backend Client는 Access token을 이용하여 Resource Server에 Resource를 요청한다.
  8. Resource 서버는 Access token을 확인하여 Resource에 대한 접근을 인허가 한다.

이 방식은 Backend Client에서 Resource 획득 후 로직을 처리한 뒤 Frontend에게 응답을 전달을 별도로 수행해야한다.

OpenID Connect(OIDC)

OIDC는 OAuth 2.0 상위에서 사용자 인증이 추가된 확장이다. 인증이 포함됬다는 것은 사용자 정보에 대한 제공을 한다는 말로 볼 수도 있다. OAuth 2.0 의 응답의 Access token을 통해 인가 수행한다면 OIDC의 결과로 나오는 응답을 통해 사용자 정보를 획득 할 수 있는 것이다.

Oauth 2.0이 아닌 아닌 OIDC를 이용한다면 응답에 id_token이라는 것이 추가된다.

  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "token_type": "Bearer",
  "scope": "email",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
  "id_token" : <JWT_ID_TOKEN>

구글의 예로 id_token을 decode 해보면

Body =
{ "iss":"accounts.google.com",
  "at_hash":"HK6E_P6Dh8Y93mRNtsDB1Q",
  "email_verified":"true",
  "sub":"10769150350006150715113082367",
  "azp":"9150833677096-....apps.googleusercontent.com",
  "email":"[email protected]",
  "aud":"9150833677096-....apps.googleusercontent.com",
  "iat":1353601026,
  "exp":1353604926,
  "nonce": "0394852-3190485-2490358",
  "hd":"example.com"
}

위와 유사한 형태로 반환된다.

OAuth 2.0를 두고 OIDC를 이용하는 이유는 사용자 정보를 획득하기 위한 절차가 한단계 감소한다는 것이다.

OAuth 2.0을 이용하여 사용자 인증을 하기 위해서는 Access Token 발급 이후 이 Token을 이용하여 Resource Server에 사용자 정보를 요청해야한다.

즉 두단계가 필요하다. OIDC는 인증이 끝나고 Access Token 요청 시 ID Token이 함께 발급되어 요청 수가 1회 줄어들게 되고 트래픽 감소 효과로 이어진다.

Access Token 자체로 인증이 완료되었다고 생각할 수도 있지만 그렇지 않다. OAuth 2.0은 언급했듯 인가를 위한 프레임워크다. Access Token 에는 사용자 정보가 최소한으로만 포함되어 있다.(Provider에 등록된 ID 정도) 또한 Access Token을 발급 받는 방법은 인증 뿐만이 아니라 Refresh Token을 이용할 수 있기 때문에 엄밀히 사용자 인증이 완료되었다고 보기 힘들다.