뜌릅
JWT 인증 (Access Token, Refresh Token, OAUTH2) 본문
앱을 사용할 때 로그인 혹은 OAUTH 기반 로그인(구글, 네이버 등등)으로 로그인을 하게 됩니다. 이때 어떤 앱은 한번 로그인하면 계속 로그인이 유지되고, 어떤경우는 오랫동안 로그인을 하지 않다가 들어가면 로그인이 풀려있기도 합니다.
인증방식은 여러가지가 있습니다.
- 쌩으로 헤더에 유저정보를 담아서 보내기
- 세션과 쿠키
- 토큰 인증 방식
- (OAUTH2 애도 사실 토큰인증)
그 인증방식중 하나(3번)가 오늘 알아볼 JWT 인증 방식입니다. (제일 많이 사용됨)
먼저 JWT는 무엇인지 알아보겠습니다.
구성요소
JWT는 "."으로 3가지 문자열로 구성되어 있습니다.
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IiIsInJvbGUiOiJST0xFX1VTRVIiLCJleHAiOjE3MTI3MTk4ODMsImVtYWlsIjoic3VuZ3UwODA0QGdtYWlsLmNvbSJ9.Sn410sc9Erd2si15mEWlWCyL86BPEBAeW0j0ZqT-OB4"}
앞에서부터 헤더(Header), 내용(Payload), 서명(signature)으로 구성되어 있습니다.
헤더(Header)
헤더는 typ와 alg 두가지의 정보를 지니고 있습니다. typ는 토큰의 타입을 지정합니다. JWT이기에 "JWT"라는 값이 들어갑니다. alg : 해싱 알고리즘을 지정합니다. 기본적으로 HMAC, SHA256, RSA가 사용되면 토큰을 검증 할 때 사용되는 signature부분에서 사용됩니다.
예시: (JSON이 이름에 포함된만큼 JSON으로 저장되어있습니다.)
{
"typ" : "JWT",
"alg" : "HS256"
}
정보(Payload)
Payload부분에는 토큰을 담을 정보가 담겨져 있습니다. 정보의 한 조각을 claim이라고 부릅니다. 이는 보통 name / value 로 JSON 형태로 저장됩니다.
클레임의 형태는 크게 3가지입니다.
1. 등록된(registered) 클레임:록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. (검색하면 나옵니다. 여기서는 생략하겠습니다.)
2. 공개(public) 클레임: 개 클레임들은 충돌이 방지된(collision-resistant)이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI형식으로 짓습니다.
{
"https://chup.tistory.com/jwt_claims/is_admin" : true
}
3. 비공개(private) 클레임: 양 측간에(보통 클라이언트 <-> 서버) 합의하에 사용되는 클레임 이름들입니다.
서명
서명은 헤더의 인코딩값과 정보(Payload)의 인코딩값을 합친후 주어진 비밀키로 해쉬를 하여 생성합니다. 이렇게 만든 해쉬를 base64형태로 나타내게 됩니다.
예시
https://jwt.io/ 에 들어가면 JWT을 내부 구조를 decoding 할수 있습니다.

JWT 인증 방식 (Access Token)
사용자는 JWT을 Access Token역할로 사용하여 서버로 보내게 됩니다. 보낼때는 주로 HTTP헤더의 Authorization에 담아서 보내게 됩니다.
아래 이미지는 Graphql 클라이언트에서 Header에 token(JWT 토큰은 아닙니다)을 담아서 API을 요청한 모습입니다.
왼쪽 개발자 도구에서 헤더를 볼 수 있습니다.

이 토큰은 서버에서 복호화가 되며 중간에 해커가 탈취하여 사용자의 정보를 수정한 후에 보내도 Secret_KEY을 알지 못한다면 서명의 복호화와 다르게 되어 인증에 실패하게 됩니다.
또한 만료기간을 설정할 수 있어서, 만료기간이 지나게 되면 인증에 마찬가지로 실패하여 다시 로그인을 해야합니다.
이것이 우리가 로그인을 하지 않아도 자동으로 인증이 되는 과정이고, 시간이 흘렀을시에 다시 로그인을 해야하는 이유중 하나가 될 수 있습니다.

1,2,3,4는 로그인 과정
로그인 후에 5,6,7은 API을 보내는 과정입니다.
1. 사용자가 로그인을 합니다.
2. 서버에서는 계정정보를 읽어 사용자를 확인 후, 사용자의 고유한 ID값을 부여한 후, 기타 정보와 함께 Payload에 넣습니다.
3. JWT 토큰의 유효기간을 설정합니다.
4. 암호화할 SECRET KEY를 이용해 ACCESS TOKEN을 발급합니다.
5. 사용자는 Access Token을 받아 클라이언트에 저장한 후, 인증이 필요한 요청마다 토큰을 헤더에 실어 보냅니다.
6. 서버에서는 해당 토큰의 Verify Signature를 SECRET KEY로 복호화한 후, 조작 여부, 유효기간을 확인합니다.
7. 검증이 완료된다면, Payload를 디코딩하여 사용자의 ID에 맞는 API 데이터를 가져옵니다.
장점
- 간편합니다. 발급후 검증만 하면 됩니다. 이는 Stateless 한 서버를 만드는 입장에서는 큰 강점입니다. 여기서 Stateless는 어떠한 별도의 저장소도 사용하지 않는, 즉 상태를 저장하지 않는 것을 의미합니다. 이는 서버를 확장하거나 유지,보수하는데 유리합니다.
- 확장성이 뛰어납니다. 토큰 기반으로 하는 다른 인증 시스템에 접근이 가능합니다. 예를 들어 Facebook 로그인, Google 로그인 등은 모두 토큰을 기반으로 인증을 합니다. 이에 선택적으로 이름이나 이메일 등을 받을 수 있는 권한도 받을 수 있습니다.
단점
- JWT는 한 번 발급되면 유효기간이 완료될 때 까지는 계속 사용이 가능합니다. 따라서 악의적인 사용자가 JWT을 탈취한 경우 유효기간이 지나기 전까지 신나게 정보들을 털어갈 수 있습니다. -> Access Token의 기한을 줄이고 Refresh Token을 도입
- Payload 정보가 제한적입니다. 위에서 언급했다시피 Payload는 따로 암호화되지 않기 때문에 디코딩하면 누구나 정보를 확인할 수 있습니다. (세션/쿠키 방식에서는 유저의 정보가 전부 서버의 저장소에 안전하게 보관됩니다) 따라서 유저의 중요한 정보들은 Payload에 넣을 수 없습니다.
JWT 인증 방식 (Refresh Token)
위의 단점보안(Access Token탈취)을 위해 Refresh Token을 도입합니다. 가끔 로그인을 한번하면 꽤나 오랜시간이 걸렸음에도 로그아웃이 안된상태로 남은 앱이나 웹사이트를 본적이 있을 것입니다. 이는 Refresh Token이 도입된것이 아닐까 추측합니다.
처음에 로그인을 완료했을 때 Access Token과 동시에 발급되는 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 발급해주는 열쇠가 됩니다(여기서 만료라는 개념은 그냥 유효기간을 지났다는 의미입니다.)
사용 예를 간단히 들어보겠습니다. Refresh Token의 유효기간은 2주, Access Token의 유효기간은 1시간이라 하겠습니다. 사용자는 API 요청을 신나게 하다가 1시간이 지나게 되면, 가지고 있는 Access Token은 만료됩니다. 그러면 Refresh Token의 유효기간 전까지는 Access Token을 새롭게 발급받을 수 있습니다.
물론 Refresh Token도 탈취 가능성이 있기에 유효기간을 지정하고 안전한곳에 보관을 합니다.

A. 로그인 인증 요청이라고 생각하면됩니다.
B. 로그인 후 Access Token과 Refresh Token을 받고, 둘다 클라이언트에 저장합니다.(Refresh 토큰은 더 안전한 곳에 저장합니다.) 서버 입장에서는 Refresh Token을 DB에 저장하기도 합니다.
C. 일반적인 API 요청입니다. 아직 Access Token이 만료전입니다.
D. 일반적인 API 응답입니다. 아직 Access Token이 만료전입니다.
E. Access 토큰이 만료된 API 요청입니다. 아직 Access Token이 만료후입니다. (API을 보내기 전에 만료된게 감지되면 자동으로 발급요청을 새로하기도 합니다.)
F. 클라이언트에 만료 토큰 에러 응답이 수신됩니다.
G. Refresh Token을 사용하여 서버에 새로 토큰발급을 요청합니다.
H. Access Token과 선택적으로 Refresh Token을 발급받고 다시 API을 요청합니다.
장점
- 보안이 강화됩니다.
단점
- 구현이 복잡합니다. 검증 프로세스가 길기 때문에 자연스레 구현하기 힘들어졌습니다(프론트엔드, 서버 모두)
- Access Token이 만료될 때마다 새롭게 발급하는 과정에서 생기는 HTTP 요청 횟수가 많습니다. 이는 서버의 자원 낭비로 귀결됩니다.
OAUTH2
인터넷 사용자들이 비밀번호를 제공하지 않고, 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수있는 개방형 표준 방법입니다.
SNS 로그인 = OAuth라고 생각하시는 분이 계시는데 이는 잘못된 생각입니다. OAuth 프로토콜의 기능 중 하나로 SNS 로그인이 있다고 합니다.
OAuth 2.0의 인증 방식은 크게 4가지 입니다.
1. Authorization Code Grant
2. Implicit Grant
3. Resource Owner Password Credentials Grant
4. Client Credentials Grant
이중 제일 많이 사용되는 1번을 설명하겠습니다.
설명에 앞서 용어정리를 하겠습니다.
사용자(Resource Owner)
- 역할: 리소스 소유자. 우리의 서비스를 이용하면서, 구글, 페이스북 등의 플랫폼에서 리소스를 소유하고 있는 사용자이다.
- 예시: 리소스라 하면 '구글 캘린더 정보', '페이스북 친구 목록', '네이버 블로그 포스트 작성' 등이 해당될 것이다.
제3자 어플리케이션(Client)
- 역할: Resource Server의 자원을 이용하고자 하는 서비스. 보통 우리가 개발하려는 서비스가 될 것이다.
- 예시: 잡플래닛, 무신사와 같이 다른 사이트를 통해 로그인이 가능한 어플리케이션을 의미한다.
인증 서버(Authentication Server)
- 역할: 인증 서버는 사용자의 신원을 확인하고, 제3자 애플리케이션(클라이언트)에 대한 인증을 수행합니다. 사용자가 제3자 애플리케이션을 통해 로그인을 시도할 때, 인증 서버는 사용자의 신원을 확인하고, 이를 기반으로 액세스 토큰(Access Token)과, 선택적으로 리프레시 토큰(Refresh Token)을 발급합니다.
- 예시: 구글 로그인을 사용할 때, 사용자가 구글 계정으로 로그인을 시도하면, 구글의 인증 서버는 사용자의 신원을 확인하고 액세스 토큰을 발급합니다.
리소스 서버(Resource Server)
- 역할: 리소스 서버는 사용자의 데이터나 서비스를 보유하고 있으며, 인증 토큰을 통해 접근 권한이 검증된 요청에 대해서만 해당 자원에 대한 접근을 허용합니다. 즉, 제3자 애플리케이션이 사용자의 데이터에 접근하고자 할 때, 리소스 서버는 제공된 액세스 토큰을 검증하고 요청된 데이터를 제공합니다.
- 예시: 사용자가 SNS 애플리케이션을 통해 자신의 구글 연락처에 접근하고자 할 때, 해당 요청은 구글의 리소스 서버로 전송되고, 리소스 서버는 제공된 액세스 토큰을 검증한 후 요청을 승인하고 연락처 데이터에 접근을 허용합니다.
OUATH2 인증과정
사전에 Redirect URL과 Client ID, Client Secret등을 발급받아 Client서버에 저장해야 합니다.

- 사용자가 로그인 요청을 합니다.(구글로 로그인을 누릅니다.)
- 클라이언트가 받은 요청을 클라이언트에 저장된 아이디와 리다이렉션 URI와 함께 인증 서버에 로그인 요청합니다. (Scope는 어떤 데이터를 요청할지를 표시합니다.)
- 인증서버는 사용자에게 로그인할수 있게 로그인창을 제공해줍니다. (구글 로그인 창)
- 로그인 정보를 입력하여 로그인합니다.
- 로그인 성공시 인증코드를 사용자에게 발급합니다.
- 사용자는 미리 설정된 Redirection Uri으로 인증코드와 함께 리다이렉션 됩니다. 이 인증코드는 매우 짧은 유효기간을 갖고있으며 Access Token을 발급받기 위한 코드입니다.
- Client에 저장된 아이디와 시크릿과 함께 Access Token을 요청합니다.
- Access Token을 발급합니다. Client는 Access Token을 DB에 저장합니다.
- 로그인에 성공합니다.
- 이 이하부터는 사용자가 필요한 리소스를 요청하면 클라이언트는 가져오는 식으로 동작합니다.
'SPRING' 카테고리의 다른 글
HIKARICP 데드락(Connection Leak 원인 찾기, feat: JMETER) (0) | 2023.06.21 |
---|---|
HIKARICP Property 설정값이 application파일에서 적용이 안됨. (0) | 2023.06.21 |
Audit Logging Column이 생성되지 않는 경우 (키중복 save) (0) | 2023.06.21 |
부하테스트 (0) | 2023.06.21 |
Soft Delete는 언제 사용하고, 어떻게 사용하나요?? (0) | 2023.06.21 |