본문 바로가기
  • Floodnut's Home Directory
보안/OAuth 2.0

[OAuth 2.0 API 보안] - OAuth 2.0

by Floodnut 2022. 5. 19.

OAuth 2.0에 대해

OAuth 2.0은 OAuth 1.0에 기반을 두었지만 OAuth WRAP(웹 리소스 인가 프로필)의 영향을 크게 받았다.

OAuth 1.0은 위임에 대한 프로토콜이고 OAuth 2.0은 인가를 위한 프레임워크이다.

1. OAuth 2.0

외부의 서드파티 애플리케이션이 서비스의 데이터에 접근하고자 한다면 OAuth Provider의 자격 증명을 서드파티 애플리케이션과 공유한다. 이를 통해 서드파티 애플리케이션이 접근 권한을 위임 받을 수 있다.

다만 이 접근 권한의 위임은 서드파티 애플리케이션의 접근을 인정하는 것이기에 큰 문제를 발생시킬 수 있다.

이에 OAuth 2.0은 이 자격증명을 서드파티 애플리케이션과 공유하지 않고 특정 시간에 임시 토큰만을 발행한다.

Django-RestFramework OAuth

위의 페이지에서 장고 서드파티 애플리케이션과 구글 Provider 간의 인가 방식을 확인할 수 있다.

간단한 동작 원리를 아래에서 확인해보자.

  1. 사용자는 웹 앱에서 구글 Provider의 기능이나 데이터에 접근하고자 한다.

    그렇다면 웹 앱은 구글 Provider로부터 인증 토큰을 받기 위해 리다이렉트된다.

  2. 구글은 인증 되어있지 않은 사용자에게 인증을 요청하고 웹 앱이 사용자의 권한에 접근할 수 있도록 동의를 요청한다.

  3. 사용자는 인증을 받고 구글의 동의 요청을 수락하면 웹 앱과 토큰을 공유한다. 이 토큰은 만료 기간이 정해져있으며 그 기간 동안 사용자의 동의로 허가된 권한 작업을 수행할 수 있다.

  4. 웹 앱은 구글로부터 전달받은 토큰을 통해 구글 API에 접근한다. 구글은 이 토큰이 유효한지 검증할 것이다.

2. OAuth 2.0의 구성

일반적인 OAuth 2.0을 통한 인가 과정에서의 구성 요소를 알아보자.

  • 자원 소유자 : 권한과 데이터를 소유하는 사용자이다.
  • 자원 서버 : 구글 API 서버와 같이 권한을 요구하는 자원들이 존재하는 곳이다.
  • 클라이언트 : 자원의 접근을 요구하는 서드파티 애플리케이션이다.
  • 인가 서버 : 자원 서버와 동일시 되거나 분리될 수 있으며 토큰을 발행하고 검증하는 곳이다.

3. 승인 방식

OAuth 2.0의 승인 방식은 자원의 소유자를 대신에서 자원에 접근할 수 있도록 동의를 구하는 절차다.

  • 인가 코드 승인

    웹 앱에서 주로 사용되며 클라이언트에서 제공하는 애플리케이션에 접근하는 자원 소유자로부터 시작된다.

      https://___DOMAIN___.com/OAuth2/authorize?respose_type=code
                          &client_id=____client_id____
                          &redirect_uri=https%3A%2F%2F__redirect__
                          &scope=_____
    

    위 URL을 보자.

    서드파티 웹 앱은 사용자의 동의를 위해 자원을 소유한 사용자를 인가 서버로 리다이렉트 시킨다.

    이 HTTP 요청을 통해 사용자는 인가 서버의 엔드포인트로 이동한다.

    여기서 파라미터들은 다음의 의미를 가진다.

    • response_type=code → 인가 서버에 인가 코드를 요청하는 것
    • client_id → 여러 클라이언트 중 요청이 들어온 클라이언트를 식별하기 위한 식별자
    • redirect_uri → 콜백 URL, 클라이언트가 요청한 코드를 반환한다.
    • scope → 클라이언트가 접근할 수 있는 권한의 범위

    client_id는 웹 앱을 의미하며 이 식별자에 대응되는 client_secret 값이 존재한다.

    등록된 클라이언트에 대해 인가 서버는 이를 알고 있다.

    클라이언트의 등록 단계에서 클라이언트 측은 인가 서버와의 요청-응답을 위한 URL을 제공한다.

    전달 받은 코드는 인가 코드라고 하며 리다이렉트를 통해 웹 앱 → 자원 소유자 순서로 전달된다.

    이 인가 코드를 통한 동의 과정으로 최종적으로 OAuth 액세스 토큰을 얻게 된다.

```bash
# 요청
# 인가코드는 일회성 코드로 사용된다.
$ curl -v -k -x POST --basic
    -u _____________________
    -H "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
    -d "grant_type=authorization_code&
            code=__인가코드__&
            redirect_uri=https://__redirect__"
            https://___DOMAIN___.com/OAuth2/token
```

```json
// 응답
{
    "token_type" :"bearer",
    "expires_in" : 3600,
    "refresh_token" : "__refresh__",
    "access_token" : "__access__"
}
```

위 요청을 통해 인가코드를 전달하고 그 다음의 응답을 전달받는다.

토큰 유형과 토큰 만료 기한, 액세스 토큰과 액세스 토큰 갱신을 위한 리프레시 토큰이다.
  • 암시적 승인

    웹 브라우저의 자바스크립트 클라이언트에서 사용되지만 자바스크립트 클라이언트도 인가 코드 승인의 사용이 권장된다. 이는 보안 문제 때문이다.

    자바스크립트 클라이언트는 사용자를 인가 서버로 리다이렉트 시키고 인가 코드를 요청하는 것이 아닌 바로 토큰을 요청한다. 이 때, 인가 서버는 클라이언트를 인증하지 않고 client_id 만을 전달받는다.

      요청
      https://___DOMAIN___.com/OAuth2/authorize?respose_type=code
                      &client_id=____client_id____
                      &redirect_uri=https%3A%2F%2F__redirect__
                      &scope=_____
    
      응답
      https://___CALLBACK___.com/#access_token=___ACCESS___&expires_in=3600

    위의 요청에 대해 바로 다음의 응답과 같이 액세스 토큰과 만료 기한이 반환된다.

    앞서 확인한 인가 코드 승인과는 다르게 절차가 줄어들었다.

    URL과 URI 프래그먼트의 값은 백엔드로 전달되지 않고 브라우저에서만 유지된다.

    따라서 클라이언트 앱이 콜백 URL로 리다이렉트를 보내더라도 프래그먼트는 전달되지 않는다.

  • 자원 소유자의 패스워드를 통한 자격증명 승인

    이 승인 방식은 자원 소유자가 클라이언트 앱을 신뢰하는 것이 전제다.

    자원 소유자가 자신의 자격증명을 클라이언트 앱에 직접 전달한다.

      # 요청
      $ curl -v -k -x POST --basic
          -u _____________________
          -H "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
          -d "grant_type=password&
                  username=__user__&
                  password=__pasword__&"
                  https://___DOMAIN___.com/OAuth2/token
      // 응답
      {
          "token_type" :"bearer",
          "expires_in" : 685,
          "refresh_token" : "__refresh__",
          "access_token" : "__access__"
      }

    인가 코드 승인의 요청과는 다르게 username과 password를 바로 파라미터로 넘긴다.

    이에 대한 응답으로는 인가 코드 승인과 동일한 응답을 반환한다.

  • 클라이언트 자격증명 승인

    클라이언트가 자원 소유자 그 자체가 되는 방식이다.

      # 요청
      $ curl -v -k -x POST --basic
          -u _____________________
          -H "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
          -d "grant_type=client_credentials"
                  https://___DOMAIN___.com/OAuth2/token
      // 응답
      {
          "token_type" :"bearer",
          "expires_in" : 3600,
          "access_token" : "__access__"
      }

    위 요청과 응답을 보면 클라이언트 자체에 대한 승인을 요청한다.

    그 응답으로는 갱신토큰을 제외한 나머지는 정상적으로 반환된다.

    이 방식은 사용자가 없는 시스템 간에서 사용하는 방식이다.

    개인적으로는 외부로 노출되지 않는 백엔드 서버 간의 인가를 위해 사용된다고 생각한다.

  • 갱신 승인

    갱신 승인은 인가를 위한 방식은 아니다.

    만료 기한이 짧은 액세스 토큰을 갱신하기 위한 요청이다.

# 요청
$ curl -v -x POST --basic
    -u _____________________
    -H "Content-Type:application/x-www-form-urlencoded;charset=UTF-8"
    -d "grant_type=refresh_token&
            refresh_token=__refresh__"
            https://___DOMAIN___.com/OAuth2/token
// 응답
{
    "token_type" :"bearer",
    "expires_in" : 3600,
    "refresh_token" : "__refresh__",
    "access_token" : "__access__"
}

앞서 살펴본 승인 유형들이 모두 사용되지는 않는다.

적절한 시스템에 필요한 승인 방식을 고려하고 선택한다.

4. OAuth 2.0 토큰

Bearer 토큰

  • 토큰을 소유한 누구나 만료기한 내에 토큰을 사용할 수 있다.

  • 이 토큰은 손실되지 않도록 TLS로 보호된 채널 내에서 사용해야 한다.

💡 Authorization: Bearer __TOKEN__

일반적으로는 위와 같이 HTTP GET 요청의 헤더 내에 토큰을 삽입하여 사용한다.

💡 GET /resource?access_token=__TOKEN__

자바스크립트 클라이언트에서 위와 같이 파라미터를 통해 접근할 수도 있다.

💡 access_token=__TOKEN__

또는, 액세스 토큰을 인코딩하고 HTTP 요청의 바디 내에 포함시킬 수도 있다.

서버에서 지원하는 방식을 적용하고 이용한다.

5. OAuth 2.0 클라이언트

OAuth 2.0은 아래 두 가지 클라이언트를 식별할 수 있다.

  • 기밀 클라이언트
  • 공용 클라이언트

위 클라이언트에서 기밀 클라이언트는 자신의 자격 증명을 보호한다. 공용 클라이언트는 이를 보호할 수 없다.

OAuth 2.0은 웹 앱, 사용자 에이전트 기반 앱, 기본 앱 세 가지 유형의 클라이언트 프로필을 기반으로 한다.

  • 웹 앱

    웹 서버에서 실행되는 기밀 클라이언트로 구별할 수 있다. 사용자는 웹 브라우저를 통해 접근한다.

  • 사용자 에이전트 기반 앱

    공용 클라이언트로 구별할 수 있다.

    웹 서버에서 다운로드한 코드를 브라우저에서 실행한다. 이 클라이언트는 자격증명을 보호할 수 없다.

  • 기본 앱

    공용 클라이언트로 구별할 수 있다.

    사용자의 제어에 따라 동작하며 이 앱 유형에서 저장된 기밀 데이터를 추출할 수 있다.

6. JWT

요청
https://___DOMAIN___.com/OAuth2/authorize?respose_type=code
                        &client_id=____client_id____
                        &redirect_uri=https%3A%2F%2F__redirect__
                        &scope=_____

다시 위와 같은 유형의 요청을 보자.

사용자가 파라미터의 값을 직접 변경한다면 인가 서버에서의 유효성 검사 또는 다른 부분에서 예상하지 못한 결과를 유발할 수 있다. 이는 무결성을 보호하지 않기 때문에 발생한다.

JWT를 사용하여 헤더, 바디, 페이로드로 구분된 토큰을 통해 무결성, 인가, 인가 요청에 대한 기밀 성을 유지할 수 있다.

  • JWS(JSON Web Signature) : 메시지나 페이로드 → 디지털 서명 또는 MAC
  • JWE(JSON Web Encryption) : 암호화된 페이로드
https://___DOMAIN___.com/authorize?
                        respose_type=code&
                        client_id=____client_id____&
                        redirect_uri=https://__sample__/request.jwt&
                        state=_____

위 요청에서 콜백 URI는 JWS나 JWE를 가리키는 링크를 전달한다.

JWT를 통해서 시크릿으로 해시화된 토큰을 검증할 수 있다.

7. 푸시된 인가 요청 (Pushed Authorization Requests)

JWT를 통한 요청의 문제점은 각 클라이언트가 엔드포인트를 인가 서버에 직접 노출시켜야한다는 것이다.

PAR를 통해서 인가 서버의 끝에서 엔드 포인트를 정의한다.

여기서 클라이언트는 브라우저를 통하지 않고 OAuth2.0 인가 요청의 파라미터를 직접 푸시한 후 브라우저를 통해 일반 인가 절차를 진행한다. 푸시된 참조는 이 시점에 전달된다.

태그

댓글0