OAuth2?
OAuth2는 권한 부여 프레임워크라고 부르며, 타사 웹 사이트나 웹이 리소스에 접근할 수 있게 허용해주는게 목적인 프레임워크이다. 종종 OAuth2를 위임 프로토콜이라고 부르기도 한다. 기본적으로 사용하는
HTTP Basic
인증을 이용한다면 모든 요청에 대해 반복적으로 자격 증명을 보내야 한다. 즉, 이 인증은 네트워크를 통해 자격 증명이 자주 공유된다는 것이고, 이는 자격 증명을 취약하게 만들고 보안을 약화하기에 애플리케이션에서 제거하는 것이 좋다. 또한, 이 방법은 다수의 애플리케이션이 존재하는 경우 사용자가 여러 암호를 기억하는 것이 어려울 수 있다. 따라서 자격 증명을 관리하는 책임을 한 시스템이 가질 수 있으면 좋다.
OAuth2를 사용하면 위와 같이 동일한 사용자를 위한 여러 개의 자격 증명을 만들 필요 없이 중앙 집중식 인증 시스템을 구축할 수 있다. 이를 통해 아키텍처가 더 간소화되고 유지 관리도 쉬워진다.
OAuth2의 구성 요소
- 리소스 서버: 사용자가 소유한 리소스를 호스팅하는 서버, 리소스는 사용자의 데이터이거나 사용자가 수행할 수 있는 작업일 수 있다. (ex: 카카오톡을 사용하는 사용자의 정보를 가지고 있는 카카오의 서버)
- 사용자(또는 리소스 소유자): 리소스 서버가 노출하는 리소스를 소유하는 개인. 일반적으로 사용자는 사용자 이름과 암호로 신원을 증명한다. (ex: 우리가 만들 서비스를 사용하는 사용자)
- 클라이언트: 사용자를 대신해 사용자가 소유한 리소스에 접근하는 애플리케이션. (ex: 우리 서비스가 카카오 로그인을 사용할 경우 우리가 만든 서비스가 클라이언트가 된다.)
- 권한 부여 서버: 클라이언트가 리소스 서버에서 제공하는 사용자의 리소스에 접근할 권한을 부여하는 애플리케이션. 권한 부여 서버는 클라이언트가 사용자 대신 리소스에 접근 권한이 있다고 결정하면 토큰을 발급한다. 클라이언트는 이 토큰을 이용해 권한 부여 서버에서 권한을 받았음을 리소스 서버에 증명한다. 리소스 서버는 유효한 토큰이 있으면 클라이언트가 요청한 리소스에 접근을 허용한다. (ex: 카카오의 인증 서버)
OAuth2를 구현하는 방법
OAuth2는 그랜트(Grant)라고 하는 토큰을 얻는 여러 방법을 제공한다.
- 승인 코드(Authorization Code)
- 암호(Resource Owner Password Credentials: ROPC)
- 갱신 토큰(Refresh Token)
- 클라이언트 자격 증명(Client Credentials)
승인 코드 그랜트
가장 많이 이용되는 OAuth2의 구현 중 하나이다. 클라이언트는 사용자가 직접 권한 부여 서버와 상호 작용해 사용자의 요청에 대한 이용 권한을 부여받도록 요청한다. 권한이 부여되면 권한 부여 서버는 클라이언트가 사용자의 리소스에 접근하는 데 이용할 수 있는 토큰을 발행한다. 승인 그랜트의 작동 단계는 아래와 같다.
1. 인증 요청 수행
클라이언트(ex: 우리가 개발하는 서비스)는 사용자가 인증해야 하는 권한 부여 서버(ex: 카카오 인증 서버)의 엔드포인트로 사용자를 리다이렉션 시킨다. 카카오에 있는 사용자 정보를 가져와야 한다고 가정했을 때 카카오는 리소스(사용자 정보)에 접근하기 위해 인증해야 하므로 사용자가 자격 증명을 입력할 수 있는 권한 부여 서버(카카오 인증 서버)의 로그인 양식이 있는 페이지를 연다.
사용자가 권한 부여 서버와 직접 상호 작용을 하는 것이므로, 클라이언트 앱으로 자격 증명을 보내는 것이 아니다.
사용자를 권한 부여 서버로 리다이렉션 할 때 클라이언트는 다음 세부 정보가 포함된 요청 쿼리로 권한 부여 엔드포인트를 호출한다.
- response_type: 클라이언트가 코드를 기대한다는 것을 권한 부여 서버에 알리는 값인 code를 포함한다. 클라이언트가 액세스 토큰을 얻으려면 코드가 필요하다.
- client_id: 애플리케이션 자체를 식별하는 클라이언트 ID 값
- redirect_uri: 인증 성공 후 사용자를 리다이렉션 할 위치를 권한 부여 서버에 알려준다. 때때로 권한 부여 서버는 각 클라이언트의 기본 리다이렉션 URI를 이미 알고 있으므로 클라이언트는 리다이렉션 URI를 보낼 필요가 없다.
- scope: 허가된 권한과 비슷하다.
- state: CSRF 보호를 위한 CSRF 토큰을 정의한다.
2. 액세스 토큰 얻기
앞선 단계에서 생성된 코드는 클라이언트가 리소스에 접근할 수 있도록 사용자가 인증했다는 클라이언트의 증명이다(승인 코드 그랜트 유형이라고 부르는 이유). 클라이언트는 토큰을 얻기 위해 코드로 권한 부여 서버를 호출한다.
1단계는 사용자와 권한 부여 서버 간에 상호 작용이며, 2단게는 클라이언트와 권한 부여 서버 간에 상호 작용이 수행된다.
1단계와 2단계에서 인증 서버를 두 번 호출하는 이유?
권한 부여 서버가 한 번에 승인 코드와 액세스 토큰을 반환하지 않는 이유는 한 번에 토큰을 반환한다면 권한 부여 서버가 실제 올바른 클라이언트에서 받았는지 확인하지 않은 액세스 토큰으로 리다이렉션 URI를 호출할 수 있기 때문에 흐름이 덜 안전하다.
따라서, 먼저 승인 코드를 보내도록 하므로 클라이언트는 액세스 토큰을 얻기 위해 자격 증명으로 자신이 누구인지 다시 증명해야 한다. 클라이언트는 액세스 토큰을 얻기 위해 아래 사항을 제시하고 호출한다.
- 사용자의 허가를 입증하는 승인 코드
- 자신이 승인 코드를 가로챈 사람이 아니며 실제로 같은 클라이언트임을 증명하는 자격 증명
클라이언트가 권한 부여 서버에 요청하는 2단계 요청에는 아래의 세부 정보가 들어가 있다.
- code: 1단계에서 받은 승인 코드. 이는 사용자가 인증받았음을 증명한다.
- client_id 및 client_secret: 클라이언트의 자격 증명.
- redirect_uri: 1단계 검증에서 사용된 것과 같다.
- grant_type: 이용된 흐름의 유형을 식별하며 authorization_code 값을 가진다. 서버가 여러 흐름을 지원할 수 있으므로 항상 현재 실행된 인증 흐름이 무엇인지 지정하는 것이 필수적이다. 서버는 이에 대한 응답으로 액세스 토큰을 반환한다. 이 토큰은 클라이언트가 리소스 서버에서 노출하는 리소스를 호출하는 데 사용할 수 있는 값이다.
3. 보호된 리소스 호출
권한 부여 서버에서 액세스 토큰을 받고 나면 클라이언트는 보호된 리소스를 호출할 수 있다. 클라이언트는 리소스 서버의 엔드포인트를 호출할 때 액세스 토큰을 사용한다.
암호 그랜트 유형
이 그랜트 유형을 리소스 소유자 자격 증명 그랜트 유형이라고도 한다. 이 유형에서 애플리케이션은 클라이언트가 사용자 자격 증명을 수집하고 이를 이용해 인증하며 권한 부여 서버에서 액세스 토큰을 얻는다. 암호 그랜트 유형은 사용자가 자신의 자격 증명을 클라이언트와 공유한다고 가정한다. 클라이언트는 이 자격 증명을 이용해 권한 부여 서버에서 토큰을 얻으며 사용자를 대신해 리소스 서버의 리소스에 접근한다.
이 인증 흐름은 클라이언트와 권한 부여 서버를 같은 조직에서 구축하고 유지 관리할 때만 이용한다.
만약 MSA로 시스템을 구축했고 확장성을 향상한 후 각 서비스에 대한 책임을 분리하기 위해 인증 책임을 다른 서비스로 분리하기로 했다고 가정했을 때, 사용자는 인증을 위해 동일한 시스템에서 동일한 시스템으로 리다이렉션 되었다가 다시 돌아오는 것이 이상하다고 생각할 수 있다. 이 상황에서 암호 그랜트 유형을 이용하면 애플리케이션이 사용자에게 로그인 양식을 보여주고 클라이언트가 서버에 자격 증명을 대신 보내 인증 과정을 처리할 수 있다. 이 유형은 아래와 같은 두 단계로 처리된다.
1. 액세스 토큰을 요청한다.
클라이언트는 사용자 자격 증명을 수집하고 권한 부여 서버를 호출해 액세스 토큰을 얻는다. 클라이언트는 액세스 토큰을 요청할 때 다음 세부 정보를 함께 보낸다.
- grant_type: password 값을 가진다.
- client_id 및 client_secret: 클라이언트가 자신을 인증하기 위한 자격 증명
- scope: 허가된 권한이라고 이해할 수 있다.
- username 및 password: 사용자 자격 증명, 일반 텍스트 형식이며, 요청 헤더의 값으로 전송된다. 클라이언트는 응답으로 액세스 토큰을 받는다. 클라이언트는 이 액세스 토큰을 이용해 리소스 서버의 엔드포인트를 호출할 수 있다.
2. 액세스 토큰을 이용해 리소스를 호출한다.
액세스 토큰을 얻은 클라이언트는 이 토큰으로 리소스 서버의 엔드포인트를 호출할 수 있으며 승인 코드 그랜트 유형을 이용할 때와 마찬가지로 헤더에 액세스 토큰을 추가한다. 이 그랜트 유형은 리소스 소유자가 클라이언트를 신뢰할 때만 가능하다.
클라이언트 자격 증명 그랜트
OAuth2가 지원하는 가장 단순한 그랜트 유형이며 사용자가 관여하지 않을 때, 즉 두 애플리케이션 간의 인증을 구현할 때 이용할 수 있다. (암호 그랜트 유형 + API 키 인증 흐름을 조합한 것과 비슷) 클라이언트 자격 증명 그랜트 유형을 처리하는 과정은 암호 그랜트 유형에서와 비슷하다. 유일한 차이는 액세스 토큰을 요청할 때 사용자 자격 증명이 필요하지 않다는 것이다. 아래와 같은 단계로 진행된다.
1. 액세스 토큰을 요청한다.
클라이언트는 액세스 토큰을 얻기 위해 다음 세부 정보와 함께 권한 부여 서버에 요청을 보낸다.
- grant_type: client_credentials 값을 가진다.
- client_id 및 client_secret: 클라이언트 자격 증명을 나타낸다.
- scope: 허가된 권한을 나타낸다. 클라이언트는 응답으로 액세스 토큰을 받는다. 클라이언트는 이 액세스 토큰을 이용해 리소스 서버의 엔드포인트를 호출할 수 있다.
2. 액세스 토큰을 이용해 리소스를 호출한다.
액세스 토큰을 얻은 클라이언트는 이 토큰으로 리소스 서버의 엔드포인트를 호출할 수 있으며 승인 코드 그랜트 유형이나 암호 그랜트 유형과 마찬가지로 요청 시 헤더에 액세스 토큰을 추가한다.
OAuth2의 허점
- 클라이언트에서 CSRF 이용: 애플리케이션이 CSRF 보호 메커니즘을 적용하지 않으면 사용자가 로그인했을 때 CSRF가 가능하다.
- 클라이언트 자격 증명 도용: 보호되지 않은 자격 증명을 저장하거나 전송하면 이를 공격자가 도용하는 위반이 발생할 수 있다.
- 토큰 재생: 토큰을 네트워크를 통해 보내는 동안 누군가 가로챌 수 있다. 이렇게 도단당한 토큰은 재사용될 수 있다.
- 토큰 하이재킹: 인증 프로세스를 방해하고 리소스에 액세스하기 위한 토큰을 훔치는 것을 의미한다.
OAuth2는 특정 구현이 아니라 프레임워크이며, 보안 취약점은 주로 잘못된 구현에서 발생한다. 스프링에선 스프링 시큐리티를 이용하는 것만으로 애플리케이션에서 이러한 취약성 대부분을 완화하는 데 도움이 된다.