OAuth

OAuth는 현대 웹과 모바일 애플리케이션의 인증 및 권한 부여 방식을 근본적으로 변화시킨 오픈 표준 프로토콜이다. 사용자의 민감한 인증 정보를 공유하지 않고도 제3자 애플리케이션이 사용자 리소스에 접근할 수 있게 해주는 이 기술은 현재 대부분의 주요 인터넷 서비스에서 필수적인 요소로 자리잡았다.

OAuth의 등장 배경과 역사

전통적인 인증 방식의 한계

OAuth가 등장하기 전, 사용자가 제3자 서비스에 자신의 리소스에 대한 접근 권한을 부여하는 방식은 상당히 제한적이었다. 가장 일반적인 방법은 사용자가 자신의 아이디와 비밀번호를 제3자 애플리케이션에 직접 제공하는 것이었다.

이 방식은 다음과 같은 심각한 보안 위험을 내포하고 있었다:

  1. 제3자 애플리케이션이 사용자의 모든 데이터에 무제한 접근 가능
  2. 접근 권한을 세부적으로 제어할 수 없음
  3. 비밀번호 변경 시 모든 연결된 서비스에 영향
  4. 제3자 애플리케이션이 사용자의 비밀번호를 저장해야 함
  5. 비밀번호 노출 시 모든 계정이 위험에 처함

OAuth의 탄생

이러한 문제를 해결하기 위해 2006년 트위터의 개발자 Blaine Cook과 Flickr의 인증 API를 개발하던 사람들이 함께 작업을 시작했다. 2007년에 초기 OAuth 드래프트가 발표되었고, 2010년에 OAuth 1.0 프로토콜이 RFC 5849로 공식화되었다.

그러나 OAuth 1.0은 구현이 복잡하고 모바일 애플리케이션 지원이 제한적이라는 문제가 있었다. 이러한 한계를 극복하기 위해 OAuth 2.0이 개발되었으며, 2012년 RFC 6749와 RFC 6750으로 표준화되었다. OAuth 2.0은 이전 버전과 호환되지 않는 완전히 새로운 프로토콜로, 현재 널리 사용되고 있는 버전이다.

OAuth 2.0의 주요 인가 흐름 (Grant Types)

OAuth 2.0은 다양한 사용 사례에 대응하기 위해 여러 인가 흐름(권한 부여 방식)을 제공한다:

인가 코드 승인 (Authorization Code Grant)

가장 일반적으로 사용되는 흐름으로, 웹 애플리케이션에 적합하다. 클라이언트 비밀키를 안전하게 보관할 수 있는 애플리케이션에 권장된다.

작동 순서:

  1. 클라이언트가 사용자를 인가 서버로 리다이렉션한다.
  2. 사용자가 인증하고 클라이언트에게 권한을 부여한다.
  3. 인가 서버가 인가 코드와 함께 사용자를 클라이언트로 리다이렉션한다.
  4. 클라이언트는 인가 코드와 자신의 비밀키를 인가 서버에 제출한다.
  5. 인가 서버는 액세스 토큰(및 선택적으로 리프레시 토큰)을 클라이언트에게 발급한다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
+----------+
| Resource |
|   Owner  |
+----------+
     ^
     |
    (2)
     |
     v
+---------+          Client Identifier      +---------------+
|         |       & Redirection URI         |               |
|         |----(1)-- Authorization Request ->|   Resource    |
| Client  |                                 |     Owner     |
|         |<---(2)-- Authorization Code -----|               |
|         |                                 +---------------+
|         |                                 
|         |                                 +---------------+
|         |----(3)-- Authorization Code ---->|               |
|         |       & Redirection URI         | Authorization |
|         |                                 |     Server    |
|         |<---(4)------ Access Token ------|               |
+---------+                                 +---------------+

암시적 승인 (Implicit Grant)

주로 클라이언트 측 JavaScript 애플리케이션을 위한 단순화된 흐름이다. 보안상의 이유로 최신 OAuth 2.0 보안 권장 사항에서는 사용을 권장하지 않는다.

작동 순서:

  1. 클라이언트가 사용자를 인가 서버로 리다이렉션한다.
  2. 사용자가 인증하고 클라이언트에게 권한을 부여한다.
  3. 인가 서버가 액세스 토큰을 URL 프래그먼트(#)에 포함하여 사용자를 클라이언트로 리다이렉션한다.
  4. 클라이언트의 JavaScript가 URL에서 액세스 토큰을 추출한다.

리소스 소유자 비밀번호 자격 증명 승인 (Resource Owner Password Credentials Grant)

사용자의 비밀번호를 직접 사용하는 방식으로, 클라이언트가 매우 신뢰할 수 있는 애플리케이션(자사 개발 앱 등)에만 사용해야 한다.

작동 순서:

  1. 사용자가 클라이언트에게 직접 사용자 이름과 비밀번호를 제공한다.
  2. 클라이언트는 이 자격 증명을 사용하여 인가 서버에 액세스 토큰을 요청한다.
  3. 인가 서버는 자격 증명을 검증하고 액세스 토큰을 발급한다.

클라이언트 자격 증명 승인 (Client Credentials Grant)

클라이언트가 자신을 대신하여(사용자 맥락 없이) 액세스 토큰을 요청할 때 사용된다. 주로 서버 간 인증에 사용된다.

작동 순서:

  1. 클라이언트가 자신의 ID와 비밀키를 사용하여 인가 서버에 액세스 토큰을 요청한다.
  2. 인가 서버는 클라이언트를 인증하고 액세스 토큰을 발급한다.

장치 코드 승인 (Device Code Grant, RFC 8628)

입력이 제한된 장치(예: 스마트 TV)를 위한 흐름으로, 사용자가 다른 장치에서 인증하는 방식.

작동 순서:

  1. 장치가 인가 서버에서 장치 코드와 사용자 코드를 요청한다.
  2. 장치는 사용자에게 사용자 코드와 확인 URL을 표시한다.
  3. 사용자는 다른 장치에서 URL을 방문하여 사용자 코드를 입력하고 인증한다.
  4. 장치는 주기적으로 인가 서버를 폴링하여 인증 상태를 확인한다.
  5. 사용자가 인증을 완료하면 인가 서버는 장치에 액세스 토큰을 발급한다.

리프레시 토큰 승인 (Refresh Token Grant)

액세스 토큰이 만료된 후 새로운 액세스 토큰을 얻는 데 사용된다.

작동 순서:

  1. 클라이언트가 리프레시 토큰을 사용하여 인가 서버에 새 액세스 토큰을 요청한다.
  2. 인가 서버는 리프레시 토큰을 검증하고 새 액세스 토큰(및 선택적으로 새 리프레시 토큰)을 발급한다.

실제 인가 코드 승인 흐름의 상세 단계

가장 일반적으로 사용되는 인가 코드 승인 흐름을 실제 HTTP 요청과 응답을 기반으로 상세히 살펴보면:

  1. 인가 요청
    클라이언트가 사용자를 인가 서버로 리다이렉션한다:

    1
    2
    3
    4
    5
    6
    7
    
    GET /authorize?
        response_type=code&
        client_id=CLIENT_ID&
        redirect_uri=REDIRECT_URI&
        scope=read&
        state=RANDOM_STATE HTTP/1.1
    Host: authorization-server.com
    

    여기서:

    • response_type=code: 인가 코드 승인 흐름을 나타낸다.
    • client_id: 클라이언트 식별자이다.
    • redirect_uri: 인가 후 리다이렉션할 URI이다.
    • scope: 요청하는 접근 범위이다.
    • state: CSRF 공격을 방지하기 위한 무작위 값이다.
  2. 사용자 인증 및 권한 부여
    사용자는 인가 서버에서 자신의 자격 증명으로 로그인하고, 클라이언트가 요청한 권한을 검토한 후 승인 또는 거부한다.

  3. 인가 코드 발급
    사용자가 권한을 승인하면 인가 서버는 사용자를 클라이언트의 리다이렉션 URI로 리다이렉션하며, 인가 코드와 원래 요청의 상태 값을 포함한다:

    1
    2
    3
    4
    
    HTTP/1.1 302 Found
    Location: https://client-app.com/callback?
        code=AUTHORIZATION_CODE&
        state=RANDOM_STATE
    
  4. 액세스 토큰 요청
    클라이언트는 인가 코드를 사용하여 인가 서버에 액세스 토큰을 요청한다:

    1
    2
    3
    4
    5
    6
    7
    8
    
    POST /token HTTP/1.1
    Host: authorization-server.com
    Content-Type: application/x-www-form-urlencoded
    Authorization: Basic BASE64(CLIENT_ID:CLIENT_SECRET)
    
    grant_type=authorization_code&
    code=AUTHORIZATION_CODE&
    redirect_uri=REDIRECT_URI
    

    여기서:

    • grant_type=authorization_code: 인가 코드 승인 흐름을 나타낸다.
    • code: 이전 단계에서 받은 인가 코드이다.
    • redirect_uri: 원래 요청에 사용된 것과 동일한 리다이렉션 URI이다.
  5. 액세스 토큰 발급
    인가 서버는 인가 코드와 클라이언트 자격 증명을 검증한 후 액세스 토큰(및 선택적으로 리프레시 토큰)을 발급한다:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    HTTP/1.1 200 OK
    Content-Type: application/json;charset=UTF-8
    Cache-Control: no-store
    Pragma: no-cache
    
    {
      "access_token": "ACCESS_TOKEN",
      "token_type": "Bearer",
      "expires_in": 3600,
      "refresh_token": "REFRESH_TOKEN",
      "scope": "read"
    }
    
  6. 보호된 리소스 접근
    클라이언트는 발급받은 액세스 토큰을 사용하여 리소스 서버의 보호된 리소스에 접근한다:

    1
    2
    3
    
    GET /api/resources HTTP/1.1
    Host: resource-server.com
    Authorization: Bearer ACCESS_TOKEN
    

    리소스 서버는 액세스 토큰을 검증하고 요청한 리소스를 반환한다.

OAuth 2.0의 보안 고려사항

OAuth 2.0은 강력한 보안 프레임워크지만, 구현 시 여러 보안 위협을 고려해야 한다:

OAuth 확장 및 관련 기술

  1. OpenID Connect (OIDC)
    OAuth 2.0 위에 구축된 ID 계층으로, 인증을 위한 표준화된 방법을 제공한다.
    OIDC는 OAuth 2.0의 권한 부여 프레임워크를 확장하여 사용자의 ID를 확인하는 방법을 추가한다.
    주요 추가 사항:

    1. ID 토큰: JWT(JSON Web Token) 형식의 토큰으로, 사용자의 ID 정보를 포함한다.
    2. 표준화된 사용자 정보 엔드포인트: 사용자 프로필 정보를 가져오는 표준 방법을 제공한다.
    3. 인증 세션 관리: 로그인 상태 및 세션 관리를 위한 추가 기능을 제공한다.
  2. JWT(JSON Web Token)
    OAuth 2.0에서 자주 사용되는 토큰 형식으로, 자체 포함된(self-contained) 방식으로 정보를 안전하게 전송한다.
    JWT는 다음 세 부분으로 구성된다:

    1. 헤더(Header): 토큰 유형과 사용된 서명 알고리즘을 정의한다.
    2. 페이로드(Payload): 클레임(토큰에 담긴 정보)을 포함한다.
    3. 서명(Signature): 토큰의 무결성을 검증하는 데 사용된다.
      JWT는 Base64Url로 인코딩된 세 부분이 점(.)으로 구분된 형태로 표현된다.
  3. OAuth 2.1
    OAuth 2.0의 모범 사례와 보안 권장 사항을 통합한 간소화된 버전으로, 현재 초안 상태이다.
    주요 변경 사항:

    1. 암시적 승인 흐름 제거: 보안상의 이유로 더 이상 권장되지 않는다.
    2. PKCE 필수화: 모든 OAuth 클라이언트에서 PKCE 사용을 의무화한다.
    3. 리다이렉션 URI 일치 요구 사항 강화: 보안을 강화하기 위해 더 엄격한 일치 규칙을 적용한다.
    4. 리프레시 토큰 회전(Refresh Token Rotation): 보안을 강화하기 위해 새 액세스 토큰을 발급할 때마다 새 리프레시 토큰을 발급한다.

실제 OAuth 구현 사례

소셜 로그인 (구글, 페이스북, 카카오 등)

사용자가 웹사이트나 앱에 소셜 미디어 계정을 사용하여 로그인할 수 있게 해주는 기능이다. OAuth를 통해 사용자는 각 사이트마다 새 계정을 만들 필요 없이 기존 계정을 활용할 수 있다.

구현 예시 (Node.js/Express):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const app = express();

// Passport 설정
passport.use(new GoogleStrategy({
    clientID: 'YOUR_GOOGLE_CLIENT_ID',
    clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET',
    callbackURL: 'http://yourdomain.com/auth/google/callback'
  },
  function(accessToken, refreshToken, profile, done) {
    // 사용자 정보 처리
    User.findOrCreate({ googleId: profile.id }, function (err, user) {
      return done(err, user);
    });
  }
));

// 인증 라우트
app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] }));

app.get('/auth/google/callback', 
  passport.authenticate('google', { failureRedirect: '/login' }),
  function(req, res) {
    // 성공적인 인증 후 홈페이지로 리다이렉션
    res.redirect('/');
  });

API 접근 권한 관리

애플리케이션이 사용자를 대신하여 API에 접근할 수 있도록 권한을 부여한다. 예를 들어, 사진 편집 앱이 사용자의 클라우드 스토리지에서 사진을 가져올 수 있게 한다.

구현 예시 (Spring Boot OAuth2 서버):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("client-id")
            .secret("client-secret")
            .authorizedGrantTypes("authorization_code", "refresh_token")
            .scopes("read", "write")
            .redirectUris("http://client-app.com/callback")
            .accessTokenValiditySeconds(3600)
            .refreshTokenValiditySeconds(86400);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager);
    }
}

마이크로서비스 간 통신

서비스 간 인증에 OAuth (특히 클라이언트 자격 증명 승인)를 사용하여 안전한 통신을 구현한다.

구현 예시 (Python/Flask 클라이언트):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

def get_token():
    token_url = 'https://auth-server.com/token'
    client_id = 'SERVICE_CLIENT_ID'
    client_secret = 'SERVICE_CLIENT_SECRET'
    
    data = {
        'grant_type': 'client_credentials',
        'scope': 'service_api'
    }
    
    response = requests.post(
        token_url,
        data=data,
        auth=(client_id, client_secret)
    )
    
    if response.status_code == 200:
        return response.json()['access_token']
    else:
        raise Exception('Token request failed')

@app.route('/api/data')
def get_data():
    try:
        token = get_token()
        
        # 토큰으로 다른 서비스의 API 호출
        api_url = 'https://resource-server.com/api/resource'
        headers = {'Authorization': f'Bearer {token}'}
        
        response = requests.get(api_url, headers=headers)
        return jsonify(response.json())
    except Exception as e:
        return jsonify({'error': str(e)}), 500

OAuth의 미래 전망

  1. 보안 강화
    OAuth는 계속해서 보안 모범 사례를 발전시키고 있다. OAuth 2.1, PKCE의 표준화, 그리고 보다 강력한 토큰 검증 메커니즘이 향후 발전 방향이다.

  2. IoT 및 새로운 플랫폼 지원
    OAuth는 IoT(사물인터넷) 장치, 음성 비서, 웨어러블 기기 등 새로운 플랫폼에 맞게 확장되고 있다. 제한된 입력 장치나 연결성이 불안정한 환경에서도 작동하는 새로운 승인 흐름이 개발되고 있다.

  3. 상호 운용성 향상
    표준화된 보안 프로필과 구현 가이드라인을 통해 서로 다른 OAuth 구현 간의 상호 운용성을 개선하는 노력이 계속되고 있다. 이는 기업이 OAuth를 보다 쉽게 채택하고 통합할 수 있게 할 것이다.


용어 정리

용어설명
리소스 소유자(Resource Owner)보호된 리소스에 접근 권한을 부여할 수 있는 엔티티. 일반적으로 최종 사용자이다.
리소스 서버(Resource Server)보호된 리소스를 호스팅하는 서버. 액세스 토큰을 사용하여 보호된 리소스 요청을 수락하고 응답한다.
클라이언트(Client)리소스 소유자를 대신하여 보호된 리소스에 접근하려는 애플리케이션. 웹 애플리케이션, 모바일 앱, 데스크톱 프로그램 등이 될 수 있다.
인가 서버(Authorization Server)리소스 소유자를 인증하고 권한을 얻은 후 클라이언트에게 액세스 토큰을 발급하는 서버이다.
액세스 토큰(Access Token)보호된 리소스에 접근하기 위해 클라이언트가 사용하는 자격 증명. 일반적으로 짧은 수명을 가지며, 특정 범위의 권한을 나타낸다.
리프레시 토큰(Refresh Token)액세스 토큰이 만료된 후 새 액세스 토큰을 얻는 데 사용되는 자격 증명. 일반적으로 긴 수명을 가지며, 모든 권한 부여 유형에서 사용되지는 않는다.
스코프(Scope)특정 액세스 토큰이 부여하는 접근 권한의 범위. 예를 들어, “읽기 전용”, “쓰기 권한” 등을 지정할 수 있다.
동의(Consent)리소스 소유자가 클라이언트에게 특정 권한을 부여하는 과정. 사용자에게 권한 요청을 명확히 표시하고 동의를 받는 단계.
리다이렉션 URI(Redirect URI)인가 서버가 처리를 완료한 후 리소스 소유자의 사용자 에이전트(브라우저)를 다시 클라이언트로 리다이렉션하는 URI.
인가 코드(Authorization Code)클라이언트가 액세스 토큰으로 교환할 수 있는 임시 코드. 인가 코드 승인 흐름에서 사용된다.

참고 및 출처