[Spring Security] (4) OAuth2의 작동

Park Yeongseo·2023년 9월 12일
0

Spring Security

목록 보기
4/13
post-thumbnail

1. OAuth2 프레임워크

OAuth2 프레임워크의 주 목적은 타사 웹사이트나 웹이 리소스에 접근할 수 있게 허용하는 것이다.

기본적으로 사용해온 HTTP Basic 인증 방식은 다음의 두 특징을 가진다.

  • 모든 요청에 자격 증명
  • 사용자의 자격 증명을 별도의 시스템이 관리

이 두 특징에서 발생할 수 있는 발생 가능한 보안 상 문제점에는 다음의 것들이 있다.

  • 네트워크를 통해 자격 증명이 자주 공유됨.
  • 클라이언트가 자격 증명을 저장해 인증 및 권한 부여 요청과 함께 자격 증명을 서버에 보낼 수 있게 함.

위 두 가지는 보안 약화에 일조하므로 애플리케이션 아키텍처에서는 제거하는 것이 좋고, 많은 경우 별도의 시스템에서 사용자 자격 증명을 관리하는 것이 낫다. 한 조직에서 이용하는 모든 앱의 자격 증명을 모두 따로 구성, 이용하게, 즉 각 앱이 별도로 자격 증명을 관리하게 할 수도 있다. 하지만 이는 관리 상의 어려움을 가진다.

자격 증명 관리의 책임을 시스템 구성 요소에 격리하고, 이 구성 요소를 권한 부여 서버라 하자. 이와 같은 권한 부여 서버를 구성하면 같은 사용자를 나타내는 중복된 자격 증명이 제거되고, 아키텍처도 간소화되며 유지 관리도 쉬워진다.

2. OAuth2 인증 아키텍처의 구성 요소

OAuth2의 구성 요소에는 다음의 것들이 있다.

  • 리소스 서버 : 사용자 소유의 리소스를 호스팅하는 서버. 여기서 리소스는 사용자 데이터, 사용자가 수행할 수 있는 작업을 가리킨다.
  • 사용자 - 리소스 소유자. 일반적으로는 사용자 이름과 암호로 신원을 증명한다.
  • 클라이언트 - 사용자를 대신해 사용자 소유의 리소스에 접근하는 앱. 클라이언트 ID, 비밀을 이용해 신원을 증명한다. 클라이언트는 자신을 증명하는 자체 자격 증명이 필요하다.
  • 권한 부여 서버 : 클라이언트가 리소스 서버가 노출하는 사용자 리소스에 접근할 권한을 부여하는 앱. 클라이언트가 사용자 대신 리소스에 접근 권한이 있다고 결정하면 토큰을 발급하고, 클라이언트는 이 토큰으로 리소스 서버에 권한을 증명한다. 리소스 서버는 유효한 토큰이 있는 클라이언트에 리소스 접근을 허용한다.

3. 구현 방법 선택

OAuth2를 이용한다는 것은 권한 부여에 토큰을 이용한다는 것이다. 토큰을 얻은 후에는 특정 리소스에 접근 가능해지는데, OAuth2는 그랜트(grant)라고 하는 토큰을 얻는 여러 방법을 제공한다.

아래는 일반적인 그랜트 유형들이다. 구현을 하려면 그랜트를 선택하고, 그랜트 유형별로 토큰 생성 방식을 알아야 하며, 앱의 요구 사항에 따라 그랜트 유형들 중 하나를 선택해야 한다.

3.1. 승인코드

(1) 인증 요청 : 사용자와 권한 부여 서버가 직접 상호 작용

클라이언트는 사용자가 인증해야 하는 권한 부여 서버의 엔드포인트로 사용자를 리디렉션, 사용자가 자격 증명을 입력할 수 있는 권한 부여서버의 로그인 양식이 있는 페이지를 연다. 기술적으로는 사용자를 권한 부여 서버로 리디렉션할 때, 클라이언트는 다음의 세부 정보가 포함된 요청 쿼리로 권한 부여 엔드포인트를 호출한다.

  • response_type 클라이언트가 코드를 기대함을 권한 부여 서버에 알리는 값인 code 를 포함
  • client_id 앱 자체를 식별하는 클라이언트 ID 값
  • redirect_uri 인증 성공 후 사용자를 리디렉션할 위치를 권한 부여 서버에 알려줌.
  • scope 허가 권한
  • state CSRF 보호를 위한 CSRF 토큰을 정의

인증에 성공하면 권한 부여 서버는 리디렉션 URI로 클라이언트를 재호출, 코드와 상태 값을 제공한다. 클라이언트는 상태 값이 요청에 보낸 것과 같은지를 검사, 다른 사람이 해당 URI를 호출하는 것이 아닌지를 확인하고, 코드를 이용해 다음 단계에서 액세스 토큰을 얻는다.

(2) 엑세스 토큰을 얻음 : 클라이언트와 권한 부여 서버 간에 상호 작용 수행

1단계에서 생성된 코드는 사용자가 리소스에 접근할 수 있도록 사용자가 인증했다는 클라이언트의 증명이다. 이후 클라이언트는 토큰을 얻기 위해 코드로 권한 부여 서버를 호출한다.

권한 부여 서버가 왜 바로 액세스 토큰을 주지 않을까?
권한 부여 서버가 실제로 올바른 클라이언트에서 받았는지 확인하지 않은 액세스 토큰으로 리디렉션 URI를 곧바로 호출하는 것은 보안 상 취약하다. 위 1단계는 사용자-권한 부여 서버 간의 상호 작용이므로 클라이언트는 다시 자신이 적합한 클라이언트임을 증명해야 할 필요가 있다.

클라이언트는 액세스 토큰을 얻기 위해 다음 사항을 제시하고 호출한다.

  • code 1단계에서 받은 승인 코드. 사용자가 인증받았음을 증명
  • client_id, client_secret 클라이언트 자격 증명
  • redirect_uri 1단계에서 사용된 것과 같음
  • grant_type 현재 실행된 인증 흐름이 무엇인지 지정. 여기선 authorization_code 값을 가짐.

서버는 요청에 대한 응답으로 access_token을 반환한다. 이 토큰은 클라이언트가 리소스 서버의 리소스를 호출하는 데 사용할 수 있다.

(3) 보호된 리소스를 호출

클라이언트는 리소스 서버의 엔드포인트를 호출할 때 권한 부여 요청 헤더에 액세스 토큰을 담아 사용한다.

승인 코드 그랜트 유형의 장단점

승인 코드 그랜트 유형은 사용자가 자신의 자격 증명을 클라이언트와 공유하지 않고도 클라이언트가 작업을 하도록 허가할 수 있다는 점에서 장점을 가진다. 하지만 승인 코드를 가로채는 경우에는 클라이언트 자체 자격 증명으로 인증을 해야 하고, 이 자격 증명 또한 도난 당할 가능성도 없지는 않다. 비록 이런 일이 실제로 일어날 가능성이 높지는 않지만 취약성을 고려해 볼 필요는 있다.

3.2. 암호

리소스 소유자 자격 증명 그랜트 유형이라고도 한다. 클라이언트가 사용자 자격 증명을 수집하고 이것으로 인증하고 권한 부여 서버에서 액세스 토큰을 얻는다. 클라이언트와 권한 부여 서버를 같은 조직에서 구축하고 유지 관리할 때만 이용한다.

앱이 사용자에게 로그인 양식을 보여주고 클라이언트가 서버에 자격 증명을 보내 인증 과정을 처리한다. 사용자는 앱에서 인증 책임을 어떻게 설계했는지 알 필요 없다.

(1) 액세스 토큰 요청

클라이언트는 사용자 자격 증명을 수집하고 권한 부여 서버를 호출해 액세스 토큰 얻는다. 요청에는 다음 정보들이 포함된다.

  • grant_type 여기서는 password
  • client_id, client_secret 클라이언트가 자신을 인증하기 위한 자격 증명
  • scope 허가 권한
  • username, password 사용자 자격 증명. 일반 텍스트 형식으로 요청 헤더의 값으로 전송.

위 요청에 대한 응답으로 클라이언트는 액세스 토큰을 받으며, 이 액세스 토큰으로 리소스 서버의 엔드포인트를 호출한다.

(2) 액세스 토큰을 이용해 리소스를 호출

클라이언트는 권한 부여 헤더에 액세스 토큰을 추가하고, 이 토큰으로 리소스 서버의 엔트포인트를 호출한다.

사용자 자격 증명의 장단점

사용자 자격 증명을 클라이언트 앱과 공유하므로 승인 코드 부여 유형보다 덜 안전하지만, 보다 직관적이기는 하다. 실제 시나리오에서는 이 그랜트 유형은 피하는 것이 좋고, 기껏해야 차선으로 생각하는 것이 좋다.

3.3. 클라이언트 자격 증명

OAuth2가 지원하는 가장 단순한 그랜트 유형으로, 사용자가 관여하지 않을 때, 두 앱 간의 인증을 구현할 때 이용 가능하다. 외부 서버가 인증할 수 있게 하고 우리 서버가 노출하는 특정 리소스를 호출할 수 있게 해야 하는 경우에 사용한다.

전체적으로는 암호 그랜트 유형과 비슷하지만, 액세스 토큰 요청에 사용자 자격 증명이 필요하지는 않다.

(1) 액세스 토큰 요청

다음 요청을 보냄

  • grant_type 여기서는 client_credentials
  • client_id, client_secret
  • scope

응답으로 액세스 토큰을 받고, 이를 이용해 리소스 서버의 엔드포인트 호출한다.

(2) 액세스 토큰을 이용해 리소스 호출

권한 부여 요청 헤더에 액세스 토큰을 추가하고 이 토큰으로 리소스 서버의 엔드포인트를 호출한다.

3.4. 갱신 토큰 ⭐

OAuth2는 토큰이 어떻게 구현되느냐에 대해서는 관여하지 않지만, 토큰은 가능한 최소한의 수명을 가지도록 해야 한다. 만약 토큰을 만료되지 않는 것으로 구현한다면 클라이언트가 같은 토큰으로 리소스 서버의 리소스를 제한 없이 호출 가능해지고, 만약 이 토큰이 분실된다면 누구든 마음껏 리소스에 접근할 수 있게 된다. 만료되지 않는 토큰은 사용자 자격 증명만큼이나 강력하다.

갱신 토큰은 새 액세스 토큰을 얻기 위해 자격 증명을 이용하는 방법의 대안이다. 이 갱신 토큰을 가지고서 클라이언트는 이용된 그랜트 유형에 따라새 액세스 토큰을 얻기 위한 흐름을 다시 실행할 수 있다.

권한 부여 서버는 재인증 필요 없이, 액세스 토큰과는 값과 용도가 다른 갱신 토큰을 발행할 수 있고, 클라이언트는 갱신 토큰으로 재인증 없이 새 액세스 토큰을 얻을 수 있다.

만약 암호 그랜트 유형에서 갱신 토큰을 이용하지 않는다면, 토큰이 만료됐을 때 사용자에게 재인증을 요청하거나 사용자의 자격 증명을 저장해야 한다. 어느 쪽이든 자격 증명의 노출 가능성이 높아지는 것은 동일하다.

한편 갱신 토큰을 이용하면, 자격 증명을 저장하거나 매번 사용자를 리디렉션할 필요 없이 갱신 토큰을 저장하고 필요할 때 이로써 새 액세스 토큰을 얻을 수 있다. 갱신 토큰은 노출이 확인되면 취소할 수도 있다.

권한 부여 서버는 승인 코드나 암호 그랜트 유형에서 액세스 토큰을 발행할 때 함께 갱신 토큰을 반환한다. 클라이언트 자격 증명에서는 사용자 자격 증명이 필요 없으므로 갱신 토큰도 사용되지 않는다.

액세스 토큰이 만료되면 다음 세부 정보가 포함된 요청을 발행한다.

  • grant_type refresh_token
  • refresh_token 갱신 토큰의 값을 가진다.
  • client_id, client_secret
  • 같거나 더 작은 허가 권한을 정의하는 scope. 더 많은 허가 권한 부여 시에는 재인증이 필요.

요청에 대한 응답으로는 새 액세스 토큰과 새 갱신 토큰을 발행한다.

4. OAuth2의 허점

  • 클라이언트에서 CSRF 이용 : CSRF 보호 메커니즘을 적용하지 않으면 사용자가 로그인 했을 때 CSRF가 가능.
  • 클라이언트 자격 증명 도용 : 보호되지 않은 자격 증명 저장, 전송 시 공격자가 도용할 수 있음
  • 토큰 재생 : 토큰은 리소스 액세스에서 쓰이는 열쇠. 가로채면 재사용될 수 있음
  • 토큰 하이재킹 - 인증 프로세스 방해, 리소스 액세스 토큰 훔치는 것. 갱신 토큰의 잠재적 취약성. 갱신 토큰을 가로채고 새 액세스 토큰을 얻는 데 이용할 수 있기 때문.

취약성은 프레임워크 위에 기능을 잘못 구현한 결과이며, 스프링 시큐리티를 이용하는 것만으로 이 대부분의 취약성은 완화될 수 있다.

0개의 댓글