JWT(JSON Web Token)란?
jwt는 인증에 필요한 정보들을 암호화 시킨 JSON 토큰을 의미한다.
따라서 JWT를 이용한 인증은 유저를 인증하고 식별하기 위한 Token 기반 인증이다.
JWT는 JSON 데이터를 Base94 URL-safe Encode를 통해 인코딩하여 직렬화한 것이고,
토큰 내부에는 개인키를 통한 전자 명이 들어있다.
Base64 URL-safe Encode란?
일반적인 Base64 Encode 를 URL에서 오류 없이 사용하도록 '+' 와 '/'를 각각 '-', '_'으로 표현한 것이다.
토큰 기반 인증에서 토큰은 토큰 자체에 사용자 정보들이 포함되어 있다는 점(self-contained)이 특징이다.
클라이언트의 상태를 알아야했던 stateful했던 환경에서 JWT를 사용하게 되면
서버가 클라이언트의 상태를 저장하지 않아도 되기 때문에 stateless 하게 설계가 가능하다
JWT를 통한 인증과정
1. 사용자가 로그인 시 로그인 아이디, 패스워드를 담아 서버에 요청
2. 서버에서 서명된 (signed) JWT토큰을 생성하여 클라이언트에 응답으로 반환
3. 클라이언트는 응답으로 반환된 JWT 토큰을 사용하여 요청 시 마다 Http Header에 JWT를 담아 요청
4. 서버에서는 요청된 Http Header의 JWT를 검증하여 토큰이 유효한지 검증 후 유효하다면 요청에 맞는 응답 반환
JWT의 구조
JWT는 header, payload, signature 3개의 부분으로 구성되어 있다.
1. header
signature를 해싱하기 위한 알고리즘 정보가 담겨 있다
2. payload
서버와 클라이언트가 주고 받는, 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있다.
payload에는 보통 Claim이라는 토큰에서 사용할 정보들이 담겨있다.
위의 playload에서 key-value 형식으로 이루어진 하나의 쌍들이 모두 claim이다.
인증 시에 토큰에서 실제로 사용될 정보를 의미한다.
여러 claim들을 JWT 토큰 생성 시에 개발자가 어떤 claim을 넣을지 정한 후 마음대로 넣을 수 있습니다.
JWT의 표준 스펙에는 7가지의 claim이 정의되어 있다.
표준 스펙에 정의된 것일뿐, 꼭 7가지를 모두 포함해야하는 것은 아니며, 실제 위의 JWT payload에서도 표준 스펙 중에서 2가지를 포함하고 있다.
아래는 JWT의 표준 스펙에 대한 정보이다
1. iss(Issurer): 토큰 발급자
2. sub(Subject): 토큰 제목 - 토큰에서 사용자에 대한 식별값이 된다.
3. aud(Audience): 토큰 대상자
4. exp(Expiration Time): 토큰 만료시간
5. nbf(Not Before): 토큰 활성 날짜 (이 날짜 이전의 토큰은 활성화 되지 않음을 보장)
6. iat(Issued At): 토큰 발급 시간
7. jti(JWT Id): JWT 토큰 식별자 (issure가 여러 명일 때 구분하기 위한 값)
위 사진에서는 JWT payload에서 두번째 claim으로 'name'을 별도로 추가했다.
이때 주의할 점은 payload에는 암호화되어 있지 않기 때문에, 민감한 정보를 담지 않아야한다.
누구나 JWT Decoding을 통해 Payload의 정보를 볼 수 있기 때문에 민감한 정보를 넣지 말고,
단순하게 식별자를 위한 정보만 담아두어야 한다.
3. signature
토큰의 유효성을 검증을 위한 문자열. 이 문자열을 통해 서버에서는 이 토큰이 유효한 토큰인지 검증할 수 있다.
JWT 구조에서 가장 중요한 부분이다.
JWT signature는 암호와되어 있기 때문에, 외부에서 위의 사진처럼 Decoding을 진행해도
실제 서명부가 나오지 않고, 암호화의 구조만 나타나게 된다.
암호화 구조를 살펴보면, 앞서 JWT 정의에 대해서 말할 때 언급되었던
basebwUrlEncode를 사용하여 header와 payload를 암호화한 것을 볼 수 있다.
그 다음은 your-256-bit-secret로, 서버가 가지고 있는 개인키를 통해 암호화되어 있다.
이렇게 서버가 가지고 있는 개인키를 통해 암호화되어 있기 때문에 외부에서 signature를 복호화할 수 없다.
JWT 구조를 통해 이해하는 JWT인증 과정
1. JWT 토큰을 클라이언트가 서버에 요청 시 Http Header에 담아 요청합니다.
2. 서버에서 Http Header의 JWT 토큰을 꺼내서 가져옵니다.
3. 클라이언트가 요청한 JWT 토큰을 서버가 가지고 있는 개인키를 가지고 Signature를 복호화합니다.
4. 복호화한 Signature의 base64UrlEncode(header) / base64UrlEncode(payload)가
각각 요청한 JWT 토큰의 header, payload와 일치하는지 검증합니다.
5. 일치한다면 인증을 허용하고, 일치하지 않는다면 인증이 실패합니다.
이 과정에서 만약 해커가 header나 payload의 값을 변조한 상태로 서버에게 요청을 보내지게 된다면,
서버 JWT 검증 단계에서 signature를 복호화했을 때의 header나 payload 값과 다르기 때문에
서버에서 인증이 실패되었다고 response를 보낼 것이다.
JWT - AccessToken & RefreshToken
보통 JWT라고 하면, 인증 시 사용되는 AccessToken을 의미하게 된다.
그렇다면 RefreshToken이라는 토큰 개념은 왜 생겼을까?
만약 해커가 AccessToken을 탈취한다면?
해커는 탈취한 AccessToken을 사용하면 접근이 모두 가능해 질것이다.
이를 해결하기 위해, AccessToken의 유효기간을 짧게 하면 해결할 수 있다.
만약 해커가 탈취를 하더라도, 그 AccessToken을 짧은 시간동안 밖에 못 쓰기 때문에 대응이 되는 것이다.
하지만 AccessToken의 유효 기간을 짧게 설정하면 유저 입장에서 매우 귀찮고 번거로울 것이다.
왜? 로그인한지 얼마 되지도 않았는데 다른 페이지를 이동할 때 다시 로그인을 해서 AccessToken을 발급받아야하기 때문이다...
따라서, 해커 탈취 문제 - 사용자의 이용성에 trade-off가 발생하는 것이다.
이런 trade-off를 해결해 주는 것이 바로 RefreshToken이다.
RefreshToken은 인증이 아닌 AccessToken을 재발급해주는 역할의 Token이다.
따라서 RefreshToken만 가지고는 인증을 성공할 수 없다.
AccessToken과 RefreshToken모두 JWT이지만, 서로 다른 역할을 한다.
AccessToken과 RefreshToken의 역할을 알아보자
1. AccessToken 역할
처음 로그인 요청시 서버에서 실제 유저의 정보가 담긴 AccessToken을 발행
클라이언트는 이 AcessToken을 저장한 후, 요청마다 AccessToken을 보내서
해당 AccessToken을 서버에서 검증 후 유효한면 요청에 맞는 응답을 진행
2. RefreshToken
처음 로그인 요청 시 서버에서 AccessToken 재발급 용도인 RefreshToken을 발행
이때, 클라이언트는 RefreshToken을 저장하지 않고 RefreshToken은 보통 서버 DB에 저장된다.
RefreshToken이 유효하면, AccessToken의 재발급을 진행한다.
RefreshToken이 어떻게 해커 탈취 문제 - 사용자 이용성 trad-off를 해결할 수 있을까?
먼저, 해커 탈취 문제 대응을 위해 AccessToken의 유효 기간을 1일에서 1시간으로 변경했다고 해보자.
만약, RefreshToken이 없다면 위에서 언급했던것처럼 사용자는 1시간마다 로그인을 해줘야한다.
여기서, RefreshToken은 유효기간이 7일이라고 해보자.
AccessToken이 1시간이 지나 만료 후 클라이언트가 요청을 보낼 때,
RefreshToken 로직이 추가되면, 서버에서는 인증 실패가 아닌 RefreshToken 검증 단계에 진입
RefreshToken이 유효하면, 그 즉시 클라이언트에게 새로운 AccessTokens을 발행해주고,
클라이언트는 그 AccessToken을 받아 재요청을 하게된다.
따라서, 사용자의 눈에는 별도의 재로그인 과정없이 AccessToken이 만료되지 않는 것처럼 동작하게 된다.
RefreshToken의 유효 기간이 7일이기 때문에,
결국 사용자는 AccessToken의 유효 기간이 7일인 것처럼 사용이 가능한 것입니다.
물론 RefreshToken도 해커에게 탈취되면 AccessToken을 해커가 재발급 받을 수 있기 때문에 위험하지만,
RefreshToken은 클라이언트에 저장되는 것이 아닌 서버 DB에 저장되기 때문에, 해커 탈취 위험이 적습니다.
이렇게 해커 탈취 문제-사용자의 이용성 trade-off를 해결하는 것입니다.
AccessToken과 RefreshToken으로 JWT토큰 검증 과정
1. 사용자가 처음 로그인할 때, 인증확인하고 서버는 로그인을 성공시키면서 클라이언트에게 AccessToken과 RefreshToken을 동시에 발급해준다.
2. 서버는 데이터베이스에 RefreshToken을 저장하고, 클라이언트는 AccessToken과 RefreshToken을 쿠키, 세션 혹은 웹 스토리지에 저장하고 요청이 있을 때마다 이 둘을 헤더에 담아서 보낸다 (이전 프로젝트에서는 RefreshToken을 쿠키에 저장했다)
3. 만일 만료된 AccessToken을 서버에 보내면, 서버는 같이 보내진 RefreshToken을 DB에 있는 것과 비교해서 일치하면 다시 AccessToken을 발급해준다.
4. 사용자가 로그아웃을 하게 되면 저장소에서 RefreshToekn을 삭제하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서다시 재발급해서 DB에 저장한다.
AccessToken 만료, RefreshToken이 만료된 케이스 별로 토큰 검사 과정을 살펴보자
case1 : access token과 refresh token 모두가 만료된 경우 → 에러 발생 (재 로그인하여 둘다 새로 발급)
case2 : access token은 만료됐지만, refresh token은 유효한 경우 → refresh token을 검증하여 access token 재발급
case3 : access token은 유효하지만, refresh token은 만료된 경우 → access token을 검증하여 refresh token 재발급
case4 : access token과 refresh token 모두가 유효한 경우 → 정상 처리
만일 RefreshToken도 만료가 된다면 사용자는 다시 로그인 해야한다. RefreshToken도 탈취 가능성이 있기때문이다. (보통 RefreshToken은 2주로 잡는 편이다)
[refresh token을 검증하여 access token 재발급]
클라이언트(쿠키, 웹스토리지)에 저장되어있는 refresh token과 서버 DB에 저장되어있는 refresh token 일치성을 확인한 뒤 access token 재발급한다.
[access token을 검증하여 refresh token 재발급]
access token이 유효하다라는 것은 이미 인증된 것과 마찬가지니 바로 refresh token 재발급한다.
근데 위에서는 클라이언트 측에서 RefreshToken을 저장하지 않는다고 했는데 쿠키, 세션, 웹 스토리지에 저장한다는 건 무슨소리..?
참고자료
https://ksh-coding.tistory.com/58
'Spring > Spring Security' 카테고리의 다른 글
Spring Security를 사용해서 회원가입, 로그인 구현하기 - 자체회원가입, oauth 활용(0) (0) | 2024.04.29 |
---|---|
Spring Security를 사용해서 로그인 구현하기 - JWT(2) (0) | 2024.04.29 |
Spring Security를 사용해서 로그인 구현하기 - JWT와 세션(0) (0) | 2024.04.27 |