1. OAuth2.0
a. OAuth2.0이란
'OAuth*(Open Authorization)*'는 인터넷 사용자들이 비밀번호를 제공하지 않고, 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로써 사용되는 접근 위임을 위한 개방형 표준을 의미합니다.
사용자의 아이디와 비밀번호 없이 접근 권한을 위임받을 수 있다는 것은, 로그인 및 개인정보 관리 책임을 'Third-Party Application(google, kakao, naver 등)'에 위임할 수 있다는 것으로, 로그인 및 개인정보 관리에 대한 책임을 위임하는 것뿐만 아니라, 부여받은 접근 권한을 통해 서드파티 프로바이더가 가지고 있는 사용자의 리소스 조회 등의 기능을 수행할 수 있습니다.
💡서드 파티 프로바디어(Third-Party Provider)란
특정 서비스나 제품을 제공하는 주체로, 일반적으로 직접 서비스나 제품을 제공하는 주체(1차)와 소비자(2차) 사이에서 역할을 수행하는 외부 업체를 의미한다.
흔히 우리에게는 '소셜 로그인'으로 잘 알려져 있습니다. 독자적인 폼 로그인을 진행하지 않더라도 카카오, 페이스북, 네이버와 같은 서드파티 프로바이더들에게 인증 과정을 위임하고 사용자를 식별해냅니다.
그 중, OAuth2라고 불리우는 이유는 당연히도 이전 버전이 존재하기 때문입니다. 2010년 IETF에서 'OAuth 1.0' 공식 표준안이 RFC 5849로 발표되었으며, OAuth의 세션 고정 공격을 보완한 'OAuth 1.0a'를 거쳐, 현재는 OAuth의 구조적인 문제점을 해결하고, 핵심 요소만을 차용한 프로토콜 WRAP(Web Ressource Access Protocol)을 기반으로 발표한 'OAuth 2.0'가 많이 사용되고 있습니다.
따라서 우리는 이러한 OAuth2.0을 통해 서드 파티 프로바이더에게 인증과 인가를 위임함으로써, 클라이언트(OAuth2 Client)에 접근 토큰(OAuth2 Access Token)을 발급하는 것에 대한 구조로 이해할 수 있습니다.
b. OAuth2.0에서 사용하는 단어 정리
단어 | 설명 | 추가 설명 |
Resource Owner | 리소스 소유자, 쉽게 표현하면 특정 서드파티 애플리케이션의 회원이면서, 우리의 서비스를 이용하고자 하는 사용자를 의미 | |
Client | 클라이언트, OAuth2와 연관된 서버(Authorization Server, Resource Server)에 대한 접근을 요청하는 서비스를 의미 | '클라이언트'라는 이름 때문에 혼동이 있을 수 있지만, OAuth2 Server에 요청을 보내는 주체가 OAuth2 Client가 되므로, 백엔드 서버가 Client에 해당됩니다. |
Provider, Server | 프로바이더, OAuth2 서버(Authorization + Resource)를 구현함으로써 OAuth2 서비스를 제공하는 서드파티 제공자를 의미 | kakao로 예를 들어보자면, 인증 서버는 카카오의 OAuth 2.0 서버(토큰 발급), 리소스 서버는 카카오의 API 서버(사용자 정보, 친구 목록 등 제공)로 구성되어있습니다. |
Authorization Server | 권한 서버, Resource Owner를 인증하며 Client에게 Access Token을 발급 | |
Resource Server | 자원 서버, 사용자의 보호된 자원을 소지하고 있는 서버로 권한 서버에서 우선 Access Token을 받아야 접근이 가능 | |
Authorization Grant | 권한 획득 자격, Resource Server에 접근 가능한 AccessToken을 발급받기 이전에 발급 받는 권한 승인 코드 | |
Access Token | 접근 토큰, 해당 접근 토큰을 통해 Resource Server로부터 Resource Owner의 정보를 획득 가능 | 해당 글에서는 별도로 발급하는 JWT Access Token도 정리하므로, 두 개념을 분리하기 위해 'OAuth2 Access Token'으로 작성한합니다. |
c. OAuth2.0 인증 방식 및 동작 과정
c-1. Authorization Code Grant Process
💡Authorization Code Grant Process / 권한 부여 코드 승인 방식
1. OAuth2.0 Client(백엔드)가 Authorization Server에 접근 권한을 요청(API 요청)한다.
1-1. 요청 시 발급한 client_id, redirect_url, response_type=code를 쿼리 파라미터로 포함한 요청을 보낸다.
1-2. 프로바이더가 제공하는 자체 로그인 폼 팝업을 출력하게 된다. (ex. 카카오 로그인 폼, 네이버 로그인 폼 등.)
2. Resource Owner가 프로바이더가 띄운 로그인 폼을 통해 로그인을 시도한다.
2-1. Authorization Server는 권한 부여 코드 요청 시 전달받은 redirect_uri로 Authorization Code(권한 부여 승인 코드)를 전달한다.
2-2. 권한 부여 승인 코드를 통해 사용자 인증 및 자원서버에 접근할 때 필요한 OAuth2 Access Token을 획득할 수 있다.
3. 권한 부여 승인 코드를 가지고, Authorization Server에 OAuth2 Access Token을 요청한다.
3-1. 요청 시 발급한 client_id, redirect_url, grant_type, code(권한 부여 승인 코드)를 쿼리 파리미터로 포함한 API 요청을 보낸다.
3-2. 성공적으로 인증되면, OAuth2 Access Token을 전달한다.
4. OAuth2 Access Token을 소지한 Client는 Resource Server에게 보호하고 있는 Resource Onwer의 사용자 자원을 요청한다.💡Access Token을 획득하는 과정에서 Authorization Code 발급 과정이 들어간 이유?
Authorization Code를 발급받는 과정이 생략되고 OAuth2 Access Token을 바로 발급받는다면,
Authorization Server는 권한 부여 코드 요청 시 전달받은 redirect_uri로 OAuth2 Access Token을 전달해야 한다.
Redirect URI을 통해 데이터를 전달하는 방법은 URI 자체에 데이터를 실어 전달하는 방법밖에 없으며, 이 방법을 사용하면 중요한 데이터인 OAuth2 Access Token이 브라우저를 통해 바로 노출되게 된다.
Authorization Code 발급 과정이 추가된 경우, redirect uri로 전달된 Authorization Code를 백엔드 레벨에서 해당 승인 코드를 포함한 API 요청으로 OAuth2 Access Token을 발급받을 수 있다.
이 과정을 통해 백엔드 사이에서OAuth2 Access Token이 전달되기 때문에 액세스 토큰의 탈취 위험이 줄어드는 등, 다른 방식에 비해 보안적으로 안전하다는 특징이 있다.
d. Spring OAuth2 Client
Spring에서는 Security와 함께 OAuth2 Client 라이브러리가 존재합니다. OAuth2 Client 라이브러리는 OAuth2 Client로써 구현해야 하는 대부분의 내용을 쉽게 구성할 수 있도록 도와주는 라이브러리입니다.
프로바이더 로그인 폼으로 이동시켜주는 엔드포인트 자동 생성부터 권한 부여 승인 코드를 기반으로 Access Token 발급 받기 기능을 상세한 구현 없이 쉽게 사용할 수 있으며, OAuth2 Access Token을 사용해 자원서버로부터 받은 자원을 다루는 서비스를 추가 구현하기만 하면 위의 프로세스를 모두 손쉽게 만들 수 있습니다.
2. JWT
a. JWT Token이란?
JWT(Json Web Token)은 말 그래도 웹에서 사용되는 JSON 형식의 토른에 대한 표준 규격입니다. 주로 사용자의 인증(authentication) 또는 인가(authorization) 정보를 서버와 클라이언트 간에 안전하게 주고 받기 위해서 사용됩니다.
위는 JWT 토큰의 구조입니다. Encoded와 Decoded로 나눠서 봅시다.
a-1. Encoded
JWT 토큰에 필요한 내용들을 담아 인코딩 된 긴 문자열로, 보통 우리가 JWT 토큰을 전달의 범위에서 얘기할 때 이 인코딩된 JWT 토큰을 의미합니다.
서버에서 클라이언트에게 인코딩 된 JWT 토큰을 전달할 때는 HTTP의 Authorization 헤더 혹은 Body에 담아서 전달하며,
클라이언트에서 서버로 JWT 토큰을 전달할 때 해당 문자열을 Authorization Header에 앞에 "Bearer"라는 문자열을 추가해 서버로 전달하는 것이 일반적입니다.
a-2. Decoded
JWT 토큰의 구성 요소로 인코딩 되기 전의 JWT 토큰의 모습입니다. JWT 토큰을 생성할 때 위와 같이 Header, Payload, Signature의 3요소를 구성해 만들게 되며, 인코딩 된 JWT 토큰을 Decode하면 Header, Payload, Signature의 값을 획득할 수 있습니다.
- 헤더(Header)
해당 토큰 스스로에 대한 메타데이터를 저장합니다.
- 페이로드(payload)
토큰에 삽입하고자 하는 데이터를 의미합니다. JWT 토큰은 정해진 인코딩 알고리즘에 의해 쉽게 디코딩됩니다. 누구나 JWT 토큰에 대해 쉽게 정보를 열람할 수 있으므로 페이로드에는 가능한 누구인지 식별 가능한 최소한의 데이터만을 넣어야 하는 것이 중요합니다.
여기서 클레임(claim)이라는 개념이 나오는데, 쉽게 표현하면 페이로드에 추가되는 정보 조각의 단위라고 이해할 수 있습니다.
예를 들어, JWT 토큰을 생성할 때 각 사용자를 구별할 수 있는 '이메일'에 대한 정보를 넣고 싶다고 가정하면 JSON 변환이 가능한 key-value의 형태로 해당 조각을 삽입하는 것입니다.
{
"sub": "1234567890",
"email": "aaaaa1234@gmail.com",
}
- 시그니쳐(Signature)
시그니쳐는 헤더와 페이로드를 각각 Base64 인코딩을 거친 문자열과 백엔드 서버가 소지하고 있는 Secret Key를 포함해서 다시 인코딩한 긴 문자열을 의미합니다. 이렇게 만들어진 시그니쳐는 JWT 토큰에 포함이 되며, 추후 JWT 토큰의 유효성을 검증할 때 사용됩니다.
Secret Key는 시그니쳐의 일부를 구성하게 되는 문자열로 랜덤한 문자열로 개발자가 생성해서 사용하면 됩니다. 인터넷 상의 Key Generator를 사용하기 보다, 키보드를 랜덤하게 길게 입력해 구성하는 것이 좋습니다.
일반적으로 Springboot 프로젝트에서 Secret Key를 application.yml에 별도로 저장해 노출되지 않도록 합니다.
b. Access Token과 Refresh Token
JWT Access Token은 JWT를 사용해 특정 서비스에 접근이 가능하도록 인증/인가의 역할을 할 수 있도록 만드는 매개체입니다.
우선, JWT 토큰을 생성하는 과정입니다.
위에서 설명한 내용대로, 헤더와 페이로드에 내용을 채워넣게 되고 시그니쳐는 이렇게 입력된 헤더 + 페이로드 + 시크릿 키를 기반으로 생성됩니다. 그리고 JWT Token은 헤더와 페이로드와 시그니쳐를 인코딩해 만들어지게 됩니다.
이렇게 만들어진 JWT Token은 클라이언트에게 Authorization 헤더 혹은 Body로 전달합니다.
그렇다면 클라이언트는 해당 JWT Token을 안전한 방법으로 보관을 하고 있다가, 서버의 API를 사용하기 위해 요청과 함께 헤더에 해당 토큰을 담아서 전송을 하게 됩니다.
💡서버에 도착한 JWT Token을 서버가 검증하는 과정
1. 서버에 도착한 JWT Token을 디코딩합니다.
2. 클라이언트로부터 받은 JWT 토큰의 시그니쳐를 원본 시그니쳐라고 지징하겠습니다.
3. 클라이언트로부터 받은 JWT 토큰의 헤더와 페이로드를 얻어옵니다.
4. 서버에 저장된 secret key를 가지고 테스트 시그니쳐를 생성해냅니다.
5. 원본 시그니쳐와 테스트 시그니쳐를 서로 비교해 동일하면 검증 통과, 다르면 검증 실패하게 됩니다.
서버는 요청과 헤더에 포함된 JWT 토큰을 가지고 우리가 아는 사용자인지, 해당 사용자가 이 서비스에 접근할 권한이 있는지 판단해야 합니다. 그리고 그러한 과정을 JWT 토큰을 검증하는 것으로 대체할 수 있습니다.
JWT 토큰을 검증했을 때 유효하다면 우리 서비스가 만들어낸 토큰이므로(시크릿 키가 노출되지 않는 이상) 인증을, 페이로드에 사용자 역할을 포함한다면 인가 역시 JWT 토큰을 통해 구현이 가능하게 됩니다.
따라서 이러한 방식으로 만들어진 JWT 토큰은 Access Token으로써 활용할 수 있는 것입니다.
JWT 토큰을 사용하면 서버에 별다른 인증 정보 저장 공간이 없더라도 자체적으로 인증/인가가 가능합니다.
이러한 점은 세션과 다른 굉장한 차별점으로 작용해 JWT Token 방식이 인기를 얻었습니다.
하지만 이런 Access Token에도 단점은 있습니다.
해당 Access Token이 다른 누군가에게 탈취되어 사용한다면 서버에서는 해당 토큰에 대한 정보를 별도로 저장하지 않기 때문에, 서버로 들어온 요청이 다른 사용자가 탈취해서 사용하는 것인지, 실제 사용자가 사용하는 것인지 구별하지 못하게 됩니다.
물론 Access Token의 관리를 제대로 못한 클라이언트에게 잘못이 있지만, 탈취가 의심되는 상황에 별도의 저장 공간이 없다면 서버측에서도 후속 조치가 어렵습니다.
이러한 단점을 최소화하기 위해 나온 아이디어가 바로, "Access Token의 만료시간을 짧게 설정해서 탈취가 되더라도 피해를 최소화 하자"라는 아이디어입니다.
해당 방법은 효과적이지만, 단순히 Access Token의 유효기간을 짧게 설정한다면 기간이 만료될 때마다 새로 로그인을 하는, 불편한 사용자 경험을 안겨주게 됩니다.
그래서 추가된 개념이 바로, 'Refresh Token'입니다.
클라이언트가 로그인을 하면, 서버는 Access Token과 Refresh Token을 같이 지급합니다. 여기서 두 토큰의 만료기간은, Access Token이 훨씬 짧고 Refresh Token은 만료 기간이 긴 구조로 이루어져있습니다.
어느 순간, Access Token이 만료되고 사용자는 해당 토큰으로 서버에 요청을 보내면 서버는 만료된 사실을 알아차리고, 요청을 반려합니다. 이때 다시 로그인을 유도하지 않고 Refresh Token을 함께 요구하며, Refresh Token이 유효하고 Access Token이 만료되었다면 새로운 Access Token을 발급해주는 구조인 것입니다.
이런 방법을 사용한다면 잦은 로그인을 유도하지 않고, 탈취된 Access Token에 대해서 최소한의 피해로 줄일 수 있습니다.
물론 이 방법 역시 Refresh Token도 함께 탈취가 된다면 서버 측에서는 손 쓸 방법이 없긴 합니다. (토큰 정보에 대한 내용의 저장 공간이 없는 경우.)
c. Refresh Token의 종류
Refresh Token은 크게 두 가지로 구현할 수 있습니다.
c-1. 기존 JWT(Claim) 방식의 Refresh Token
기존의 JWT 토큰을 활용해서 Refresh Token을 만들 수 있습니다. 기존에 생성하던 방식 그대로 생성이 가능하며, 만료기한 역시 쉽게 설정할 수 있습니다. Access Token과는 별개로 새로운 Access Token을 발급 받기 위한 토큰이 존재하므로 Access Token에 들어갔던 일부 클레임 역시 제거해도 됩니다.
다만 Access Token과 Refresh Token이 모두 탈취되었을 경우에는 별도로 손 쓸 방법이 없습니다. 여전히 JWT 토큰은 Stateless 이기 때문에.
이러한 문제를 해결하기 위해 Refresh Token을 Redis 같은 DB에 저장해놓고 서버가 잘못된 요청인지 확인할 수 있는 방법을 자주 사용한다고 합니다.
c-2. UUID를 활용한 Refresh Token
UUID를 랜덤하게 생성해 해당 문자열을 Refresh Token으로 사용하는 방법이 있습니다. 다만 UUID는 JWT 토큰이 아니기 때문에 자체로 만료 시간을 결정할 수 없습니다. 따라서 해당 방법을 사용한다는 것은 곧 서버에 별도의 토큰 저장 공간을 마련한다는 의미와 동일합니다.
'Programming > Spring' 카테고리의 다른 글
[Spring] 동시성 처리 (18) | 2024.11.15 |
---|