Spring Security - OAuth 2(1): Grant 유형
in DEV on SpringSecurity, Oauth2, Grant-type
이 포스트에서는 OAuth 2 에 대해 알아본다.
클라이언트가 백엔드 애플리케이션에서 노출한 엔드포인트를 호출하기 위해 액세스 토큰을 얻는 흐름에 대해 설명한다.
OAuth 2 에 관한 내용은 스프링 시큐리티의 표준 권한 부여 및 인증 아키텍처와 기반이 같다.
- OAuth 2 를 구현하기 위한 기반 내용
목차
- 1. OAuth 2 프레임워크의 필요성
- 2. OAuth 2 인증 아키텍처 구성 요소
- 3. OAuth 2 의 Grant 유형
- 4. OAuth 2 취약점
- 참고 사이트 & 함께 보면 좋은 사이트
1. OAuth 2 프레임워크의 필요성
OAuth 2 는 권한 부여 프레임워크라고 불리며, 타 웹사이트나 웹이 리소스에 접근할 수 있게 허용하는 것이 주 목적이다.
OAuth 2 는 특정 구현이나 라이브러리가 아님
HTTP Basic 인증 방식은 모든 요청에 자격 증명을 보내야 한다. 이는 네트워크를 통해 자격 증명을 자주 공유한다는 의미이기 때문에 자격 증명을 취약하게 만들고, 보안을 약화시킨다.
대부분의 경우 별도의 시스템에서 사용자 자격 증명을 관리하는 것이 좋다.
만일 조직에서 이용하는 모든 애플리케이션이 자격 증명을 각각 따로 구성/관리된다면 사용자는 모든 암호를 기억해야 하고, 조직도 여러 자격 증명을 관리해야 한다.
자격 증명을 관리하는 책임을 시스템의 한 구성 요소(권한 부여 서버)에 격리시키는 것이 좋다. 그럼 같은 사용자를 나타내는 중복된 자격 증명도 제거되어 아키텍처가 더 간소화되고 유지 관리도 쉬워진다.
2. OAuth 2 인증 아키텍처 구성 요소
OAuth 2 의 주 구성 요소는 아래와 같다.
- 리소스 서버
- 사용자가 소유한 리소스를 호스팅하는 서버
- 리소스는 사용자의 데이터이거나 사용자가 수행할 수 있는 작업일 수 있음
- 리소스 소유자 (사용자)
- 리소스 서버가 노출하는 리소스를 소유하는 개인
- 일반적으로 사용자는 사용자 이름/암호로 신원을 증명함
- 클라이언트
- 사용자가 소유한 리소스에 접근하는 애플리케이션
- 클라이언트는 클라이언트 ID 와 클라이언트 암호를 이용하여 신원을 증명 (사용자 자격 증명과 다름)
- 클라이언트는 요청 시 자신을 증명하는 자체 자격 증명이 필요함
- 권한 부여 서버
- 클라이언트가 리소스 서버가 노출하는 사용자의 리소스에 접근할 권한을 부여하는 애플리케이션
- 클라이언트가 리소스에 접근 권한이 있다고 검증되면 토큰 발급
- 클라이언트는 이 토큰을 이용하여 권한 부여 서버로부터 권한을 받았음을 리소스 서버에 증명함
- 리소스 서버는 유효한 토큰이 있으면 클라이언트가 요청한 리소스에 접근할 수 있도록 허용함
3. OAuth 2 의 Grant 유형
OAuth 2 에는 여러 가지의 인증 흐름이 있기 때문에 애플리케이션에 적합한 흐름을 선택해야 한다.
OAuth 2 를 이용한다는 것은 권한 부여에 토큰을 이용한다는 뜻이며, 토큰을 얻은 후에 특정 리소스에 접근할 수 있다.
OAuth 2 는 그랜트 (Grant) 라고 하는 토큰을 얻는 몇 가지 방법을 제공하며, 아래는 OAuth 2 그랜트의 유형이다.
<OAuth 2 그랜트 유형>
- 승인 코드
- 암호
- 클라이언트 자격 증명
- 갱신 토큰
OAuth 2 구현을 하려면 그랜트를 선택해야 하므로 그랜트 유형별로 토큰이 생성되는 방식을 알아야 한다.
이제 각 그랜트 유형과 그러한 유형이 적용되는 대상에 대해 알아본다.
3.1. 승인 코드 그랜트 유형
승인 코드 그랜트 유형은 가장 많이 이용되는 OAuth 2 흐름 중 하나이다.
승인 코드 그랜트 유형은 아래와 같은 순서로 동작한다.
- 인증 요청
- 액세스 토큰 획득
- 보호된 리소스 호출
위 그림에서 화살표는 HTTP 요청과 응답이 아닌 OAuth 2 구성 요소 간에 교환되는 메시지이다.
1.2 권한 부여 서버에 권한 요청 지시 의 경우 클라이언트는 사용자를 권한 부여 서버의 로그인 페이지로 리디렉션한다.
2.2 인증 성공 시 액세스 토큰 제공 의 경우 권한 부여 서버는 리디렉션 URI 로 클라이언트를 호출한다.
승인 코드 그랜트 유형의 장점과 취약점
승인 코드 그랜트 유형은 사용자가 자신의 자격 증명을 클라이언트와 공유하지 않고도 클라이언트가 특정 작업을 할 수 있도록 허가할 수 있다는 장점이 있음
만일 승인 코드가 탈취되어도 클라이언트는 자체 자격 증명을 인증해야 하므로 문제없음하지만 클라이언트 자격 증명도 탈취될 수도 있는데 이런 경우는 거의 없지만 이런 취약성을 완화하려면
PKCE (Proof Key for Code Exchange)
승인 코드 그랜트 유형을 이용하는 더 복잡한 시나리오를 이용하는 방법이 있음
3.1.1 승인 코드 그랜트 유형으로 인증 요청 수행: 사용자와 권한 부여 서버 간의 상호 작용
클라이언트는 사용자가 인증해야 하는 권한 부여 서버의 엔드포인트로 사용자를 리디렉션한다. (= 클라이언트는 사용자가 직접 권한 부여 서버와 상호 작용해서 사용자 요청에 대한 권한을 부여받도록 요청함)
예) 사용자가 자격 증명을 인증할 수 있는 권한 부여 서버의 로그인 양식이 있는 페이지를 염
사용자는 직접 권한 부여 서버와 상호 작용하며, 클라이언트 앱으로 자격 증명을 보내지 않는다.
사용자를 권한 부여 서버로 리디렉션할 때 클라이언트는 아래 정보가 포함된 요청 쿼리로 권한 부여 엔드포인트를 호출한다.
response_type
- 클라이언트가 코드를 기대한다는 것을 권한 부여 서버에 알리는 값인 ‘code’ 문자열
- 클라이언트가 액세스 토큰을 얻으려면 코드가 필요함
client_id
- 애플리케이션 자체를 식별하는 클라이언트 ID
redirect_uri
- 인증 성공 후 사용자를 리디렉션할 위치를 권한 부여 서버에게 알려줌
- 종종 권한 부여 서버가 각 클라이언트의 기본 리디렉션 URI 를 이미 알고 있기 때문에 이때는 리디렉션 URI 를 보낼 필요 없음
scope
- 허가된 권한
state
- CSRF (사이트 간 요청 위조) 보호를 위한 CSRF 토큰 정의
사용자 자격 증명 인증에 성공하면 권한 부여 서버는 리디렉션 URI 로 클라이언트를 호출하여 승인 코드와 상태값을 제공한다.
클라이언트는 상태값이 요청에 보낸 것과 같은지 검사하여 다른 사람이 리디렉션 URI 를 호출하려는 것이 아닌지 확인한다.
클라이언트는 권한 부여 서버로부터 받은 승인 코드를 이용하여 액세스 토큰을 얻는다.
3.1.2. 승인 코드 그랜트 유형으로 액세스 토큰 획득: 클라이언트와 권한 부여 서버 간의 상호 작용
위에서 생성된 승인 코드는 리소스에 접근할 수 있도록 사용자가 인증했다는 클라이언트의 증명이다.
그래서 이 그랜트 유형을 승인 코드 그랜트 유형이라고 부른다.
클라이언트는 토큰을 얻기 위해 승인 코드로 권한 부여 서버를 호출한다.
<권한 부여 서버를 2번 호출하는 이유!>
- 권한 부여 서버는 사용자와 직접 상호했다는 증거로 승인 코드 생성
- 클라이언트는 이 승인 코드를 받은 후 자신의 자격 증명과 함께 액세스 토큰을 받기 위한 인증을 함
- 클라이언트는 액세스 토큰을 이용하여 리소스 서버에 접근
액세스 토큰을 얻기 위해서는 승인 코드를 보내야 하므로 클라이언트는 자신을 자격 증명하게 된다.
클라이언트가 액세스 토큰을 얻기 위해 권한 부여 서버에 아래 정보를 포함하여 요청한다.
code
- 사용자가 인증받았음을 증명하는 승인 코드
client_id
,client_secret
- 클라이언트의 자격 증명
redirect_uri
- 위에서 사용한 것과 동일
grant_type
- 흐름의 유형을 식별하며,
authorization_code
값을 가짐 - 서버가 여러 흐름을 지원할 수 있으므로 현재 실행된 인증 흐름이 무엇인지 지정하는 것이 필수임
- 흐름의 유형을 식별하며,
권한 부여 서버는 이에 대한 응답으로 액세스 토큰을 반환한다.
클라이언트는 이 액세스 토큰을 이용하여 리소스 서버에 접근한다.
암시적 그랜트 유형 (Implicit grant type)
OAuth 2 는 권한 부여 서버가 승인 코드 없이 바로 액세스 토큰을 반환하는 암시적 그랜트 유형도 제공하는데 이는 권한 부여 서버가 실제 올바른 클라이언트에서 받았는지 확인되지 않은 액세스 토큰으로 리디렉션 URI 를 바로 호출한다는 부분에서 덜 안전하기 때문에 권장하지 않음
현재 대부분의 권한 부여 서버에서 허용되지 않음
3.1.3. 승인 코드 그랜트 유형으로 보호된 리소스 호출
권한 부여 서버에서 액세스 토큰을 받으면 이제 이 액세스 토큰을 권한 부여 요청 헤더에 넣어 클라이언트는 리소스 서버에 접근이 가능하다.
3.2. 암호 그랜트 유형
암호 그랜트 유형은 사용자 자격 증명을 클라이언트 앱과 공유하기 때문에 승인 코드 그랜트 유형보다 덜 안전함
실제 운영 시엔 암호 그랜트 유형을 사용하지 않는 것을 권장함
권한 부여 서버와 클라이언트를 같은 조직에서 구추구했어도 승인 코드 그랜트 유형을 먼저 고려하는 것이 좋음
암호 그랜트 유형은 리소스 소유자 자격 증명 그랜트 유형이라고도 한다.
암호 그랜트 유형은 아래와 같은 순서로 동작한다.
- 액세스 토큰 요청
- 액세스 토큰으로 리소스 호출
클라이언트가 사용자 자격 증명을 수집하고(= 사용자가 자신의 자격 증명을 클라이언트와 공유), 이를 이용하여 인증하며 권한 부여 서버에서 액세스 토큰을 얻는다.
Spring Security - BE, FE 분리된 설계의 애플리케이션 구현 가 암호 그랜트 유형과 아주 비슷하다.
OAuth 2 암호 그랜트 유형의 예시는
Spring Security - OAuth 2(2): 권한 부여 서버 구현, Spring Security - OAuth 2(3): JWT 와 암호화 서명
을 참고하세요.
리소스 서버가 액세스 토큰을 검증하는 방법은 1.3. JWT 를 이용하는 리소스 서버 구현 을 참고하세요.
암호 그랜트 유형은 클라이언트와 권한 부여 서버를 같은 조직에서 구축 및 유지 관리할 때만 이용한다.
같은 조직에서 구축 및 유지관리할 때 승인 코드 그랜트 유형을 이용한다면 사용자는 동일한 시스템으로 리디렉션 되었다가 다시 돌아오는게 이상하다고 생각할 수도 있기 때문이다.
암호 그랜트 유형을 이용하면 애플리케이션이 사용자에게 로그인 양식을 보여주고, 클라이언트가 서버에 자격 증명을 보내서 인증 과정을 처리한다.
3.2.1. 암호 그랜트 유형으로 액세스 토큰 요청
클라이언트는 사용자 자격 증명을 수집하고, 권한 부여 서버를 호출하여 액세스 토큰을 얻는다.
클라이언트가 액세스 토큰을 요청할 때는 아래 정보를 포함하여 요청한다.
grant_type
- ‘password’ 문자열
client_id
,client_secret
- 클라이언트의 자격 증명
scope
- 허가된 권한
username
,password
- 사용자 자격 증명, 일반 텍스트 형식으로 요청 헤더의 값으로 전송됨
클라이언트는 응답으로 액세스 토큰을 받는다.
3.2.2. 암호 그랜트 유형으로 액세스 토큰을 이용해 리소스 호출
권한 부여 서버에서 액세스 토큰을 받으면 이제 이 액세스 토큰을 권한 부여 요청 헤더에 넣어 클라이언트는 리소스 서버에 접근이 가능하다.
3.3. 클라이언트 자격 증명 그랜트 유형: API 서버 간 통신 시
클라이언트 자격 증명은 OAuth 2 가 지원하는 가장 단순한 그랜트 유형이다.
사용자가 없을 때, 즉 서버 간 통신 시 인증을 구현할 때 이용한다.
4. 필터 체인의 다른 필터 위치에 필터 추가 에서 API 키를 이용하여 인증하는 맞춤형 필터 예시가 있다.
OAuth 2 를 이용하면 맞춤형 필터로 확장하는 것보다 OAuth 2 프레임워크를 이용하는 것이 더 깔끔하다.
클라이언트 자격 증명 그랜트 유형은 아래와 같은 순서로 동작한다.
- 액세스 토큰 요청
- 액세스 토큰으로 리소스 호출
클라이언트가 리소스 소유자(사용자) 를 대신하지 않고 리소스 서버에 접근한다.
클라이언트 자격 증명 그랜트는 암호 자격 그랜트와 비슷하며, 유일한 차이는 액세트 토큰을 요청할 때 사용자 자격 증명이 필요하지 않다는 것이다.
3.3.1. 클라이언트 자격 증명 그랜트 유형으로 액세스 토큰 획득
클라이언트가 엑세스 토큰을 요청할 때는 아래 정보를 포함하여 요청한다.
grant_type
- ‘client_credentials’ 문자열
client_id
,client_secret
- 클라이언트의 자격 증명
scope
- 허가된 권한
클라이언트는 응답으로 액세스 토큰을 받는다.
3.3.2. 클라이언트 자격 증명 그랜트 유형으로 액세스 토큰을 이용해 리소스 호출
권한 부여 서버에서 액세스 토큰을 받으면 이제 이 액세스 토큰을 권한 부여 요청 헤더에 넣어 클라이언트는 리소스 서버에 접근이 가능하다.
3.4. 갱신 토큰으로 새 액세스 토큰 획득
OAuth 2 인증의 결과는 Grant 라고 불리는 액세스 토큰이다.
갱신 토큰은 새로운 액세스 토큰을 얻기 위해 자격 증명을 이용하는 방법에 대한 대안이다.
토큰은 구현 방식에 따라 만료될 수도 있고, 토큰의 수명을 무한하게 만들 수도 있지만 가능하면 토큰은 최소한의 수명을 갖는 것이 좋다.
만일 만료되지 않는 토큰일 경우 토큰이 탈취될 경우 탈취된 토큰으로 리소스 접근이 가능하다.
이러한 상황을 방지하기 위해 토큰의 수명은 짧게 해야하며, 만료된 토큰은 더 이상 이용할 수 없어야 한다.
갱신 토큰의 예시는 Spring Security - OAuth 2(2): 권한 부여 서버 구현 을 참고하세요.
액세스 토큰이 만료되었을 경우 사용자가 다시 로그인할 필요가 없도록 클라이언트는 갱신 토큰을 이용하여 새로운 액세스 토큰을 발급받는다.
권한 부여 서버는 사용자가 재로그인 할 필요가 없도록 액세스 토큰과는 다른 용도인 갱신 토큰을 발행할 수 있고, 이 갱신 토큰으로 재인증없이 새로운 액세스 토큰을 얻을 수 있다.
암호 그랜트 유형에서 갱신 토큰을 이용하지 않는다면 사용자에게 재인증을 요청하거나 사용자의 자격 증명을 저장해야 하는데 암호 그랜트 유형에서 사용자 자격 증명을 저장하는 것은 사용자 자격 증명이 유출될 수 있으므로 보안적으로 심각한 문제이다.
이럴 경우 갱신 토큰을 이용하여 사용자 자격 증명을 저장하거나 매번 사용자 자격 증명 인증을 요청할 필요없이 갱신 토큰을 저장하고, 필요할 때 갱신 토큰을 이용하여 새로운 액세스 토큰을 얻는다.
권한 부여 서버는 승인 코드 그랜트 유형, 암호 그랜트 유형을 사용할 때 액세스 토큰과 함께 갱신 토큰을 반환한다.
클라이언트 자격 증명 그랜트 유형은 사용자 자격 증명이 필요없으므로 갱신 토큰도 사용되지 않는다.
갱신 토큰을 가진 클라이언트는 액세스 토큰이 만료될 때 아래 정보를 포함하여 새로운 액세스 토큰 발급을 요청한다.
grant_type
- ‘refresh_token’ 문자열
refresh_token
- 갱신 토큰값
client_id
,client_secret
- 클라이언트의 자격 증명
scope
- 기존보다 같거나 더 작은 허가 권한
- 더 많은 허가 권한을 부여해야 할 경우엔 재인증 필요
클라이언트는 응답으로 새로운 액세스 토큰과 새로운 갱신 토큰을 받는다.
4. OAuth 2 취약점
OAuth 2 를 이용할 때 어떤 문제점이 생길 수 있는지 알아야 방지도 가능하다.
- 클라이언트에서 CSRF 이용
- 애플리케이션이 CSRF 보호 메커니즘을 적용하지 않으면 사용자가 로그인했을 때 CSRF 가 가능함
- CSRF 보호를 구현하는 방법은 Spring Security - CSRF (Cross-Site Request Forgery, 사이트 간 요청 위조) 참고
- 클라이언트 자격 증명 도용
- 보호되지 않은 자격 증명을 저장하거나 전송하면 이를 공격자가 도용하는 상황이 발생할 수 있음
- 토큰 재생
- 액세스 토큰이 탈취되어 원하는 만큼 리소스를 이용하는 것을 토큰 재생이라고 함
- 토크 하이재킹
- 토큰 탈취를 말함
- 갱신 토큰을 탈취하여 새로운 액세스 토큰을 얻는데 이용할 수 있음
참고 사이트 & 함께 보면 좋은 사이트
본 포스트는 로렌티우 스필카 저자의 스프링 시큐리티 인 액션을 기반으로 스터디하며 정리한 내용들입니다.
- 스프링 시큐리티 인 액션
- Configuration Migrations
- Spring Boot 3.x + Security 6.x 기본 설정 및 변화
- 스프링 부트 2.0에서 3.0 스프링 시큐리티 마이그레이션 (변경점)