JWT로 로그인/로그아웃 구현하기
토큰 기반 인증이란?
사용자 인증 확인 방법에는 서버 기반 인증과 토큰 기반 인증이 있다.
- 세션 기반 인증
- 스프링 시큐리티에서는 기본적으로 세션 기반 인증을 제공한다.
- 세션 기반 인증을 사용해 사용자마다 사용자의 정보를 담은 세션을 생성하고 저장해서 인증을 합니다.
- 이를 세션 기반 인증이라고 한다.
- 토큰 기반 인증
- 토큰을 사용하는 방법입니다.
- 토큰은 서버에서 클라이언트를 구분하기 위한 유일한 값인데
- 서버가 토큰을 생성해서 클라이언트에게 제공하면
- 클라이언트는 이 토큰을 갖고 있다가 여러 요청을 이 토큰과 함께 신청합니다.
- 그럼 서버는 토큰만 보고 유효한 사용자인지 검증한다.
토큰을 전달하고 인증 받는 과정
토큰은 요청과 응답에 함께 보냅니다.
다음은 클라이언트와 서버가 토큰을 주고 받으며 통신하는 과정입니다.
- 클라이언트가 아이디와 비밀번호를 서버에게 전달하면서 인증을 요청하면
- 서버는 아이디와 비밀번호를 확인해 유효한 사용자인지 검증한다.
- 유효한 사용자면 토큰을 생성해서 응답합니다.
- 클라이언트는 서버에서 준 토큰을 저장합니다.
- 이휴 인증이 필요한 API를 사용할 때 토큰을 함께 보냅니다.
- 그러면 서버는 토큰이 유효한지 검증합니다.
- 토큰이 유효하다면 클라이언트가 요청한 내용을 처리합니다.
토큰 기반 인증의 특징
토큰 기반 인증은 무상태성, 확장성, 무결성이라는 특징이 있다.
1. 무상태성 : (사용자 인증이 서비스 클라이언트의 담당)
- 무상태성은 사용자의 인증 정보가 담겨 있는 토큰이 서버가 아닌 클라이언트에 있으므로 서버에 저장할 필요가 없다.
- 서버가 뭔가 데이터를 유지하고 있으려면 그만큼 자원을 소비해야한다.
- 그런데 토큰 기반 인증에서는 클라이언트에서 인증 정보가 담긴 토큰을 생성하고 인증한다.
- 클라이언트에서는 사용자의 인증 상태를 유지하면서 이후 요청을 처리해야 하는데 이것을 상태를 관리 한다고 한다.
- 서버 입장에서는 클라이언트의 인증 정보를 저장하거나 유지하지 않아도 되기 때문에
- 완전한 무상태(stateless)로 효율적인 검증을 할 수 있습니다.
2. 확장성 : (서버에서 상태 관리을 하지 않는다, 클라이언트의 토큰을 이용한 확장성)
- 무상태성은 확장성에 영향을 줍니다.
- 서버를 확장할 때 상태 관리를 신경 쓸 필요가 없으니
- 서버 확장에도 용이하다.
- 예를 들어
- 결제를 위한 서버와 주문을 위한 서버가 분리되어 있다고 가정해보겠습니다.
- 세션 인증 기반은 각각 API에서 인증을 해야되는 것과 달리
- 토큰 기반 인증에서는 토큰을 가지는 주체는 서버가 아니라 클라이언트이기 때문에
- 가지고 있는 하나의 토큰으로 결제 서버와 주문 서버에게 요청을 보낼수 있다.
- 추가로 페이스북 로그인, 구글 로그인 같이 토큰 기반 인증을 사용하는 다른 시스템에 접근해
- 로그인 방식을 확장할 수도 있고
- 이를 활용해 다른 서비스에 권한을 공유할 수도 있다.
3. 무결성 : (변경불가)
- 토큰 방식은 HMAC (hash-based message authentication ) 기법이라고도 부르는 데
- 토큰을 발급한 이후에는 토큰 정보를 변경하는 행위는 할 수 없습니다.
- 즉 토큰의 무결성이 보장된다.
- 만약 누군가 토큰을 한 글자라도 변경하면 서버에서는 유효하지 않은 토큰이라고 판단을 하는 것이다.
JWT
JWT를 이용해 인증을 하려면 HTTP 요청 헤더 중에 Authorization 키값에 { Bearer + JWT 토큰값 }을 넣어 보내야 한다.
JWT는 "." 을 기준으로 헤더(header), 내용(payload), 서명(signature)으로 이루어 져있다.
헤더내용서명
aaaaa | bbbbb | ccccc |
JWT - 헤더
헤더에는 토큰의 타입과 해싱 알고리즘을 지정하는 정보를 담습니다.
다음의 경우 JWT 토큰, HS256 해싱 알고리즘을 사용한다는 내용입니다.
{
"typ": "JWT",
"alg": "HS256"
}
이름설명
typ | 토큰의 타입을 지정합니다. JWT라는 문자열이 들어가게 됩니다. |
alg | 해싱 알고리즘을 지정합니다. |
JWT - 내용
- 내용에는 토큰과 관련된 정보를 담습니다. 내용의 한 덩어리를 클레임(claim)이라고 부른다.
- 클레임은 키값의 한 쌍으로 이루어져 있습니다.
- 클레임은 등록된 클레임, 공개 클레임, 비공개 클레임으로 나뉜다.
JWT - 내용의 한 덩어리를 클레임 - 등록된 클레임
이름설명
iss | 토큰 발급자(issuer) |
sub | 토큰 제목(subject) |
aud | 토큰 대상자(audience) |
exp | 토큰의 만료 시간(expiration), 시간은 NumericDate 형식으로 하며, 항상 현재 시간 이후로 설정합니다. |
nbf | 토큰의 활성 날짜와 비슷한 개념으로 nbf는 Not Before를 의미합니다. NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다. (활성 날짜) |
iat | 토큰이 발급된 시간으로 iat은 issued at을 의미한다. |
jti | JWT의 고유 식별자로서 주로 일회용 토큰에 사용합니다. |
JWT - 내용의 한 덩어리를 클레임 - 공개 클래임(public claim)
- 공개 클래임은 공개되어도 상관없는 클레임을 의미한다.
- 보통 클레임 이름을 URL로 짓습니다.
JWT - 내용의 한 덩어리를 클레임 - 비공개 클래임 (private)
- 비공개 클레임(private)은 공개되면 안 되는 클레임을 의미한다.
- 클라이언트와 서버 간의 통신에 사용됩니다.
{
"iss": "ajufresh@gmail.com", //등록된 클레임
"iat": 1622370878, //등록된 클레임
"exp": 1622372678, //등록된 클레임
"https://shinsunyoung.com/jwt_claims/is_admin": true, // 공개 클레임
'email": "ajufresh@gmail.com", //비공개 클레임
'hello": "안녕하세요!" //비공개 클레임
}
JWT - 서명
- 서명은 해당 토큰이 조작되었거나 변경되지 않았음을 확인하는 용도로 사용하며
- 헤더의 인코딩 값과 내용의 인코딩값을 합친 후에 주어진 비밀키를 사용해 해시값을 생성한다.
토큰 유효기간
- 만약 토큰을 주고받는 환경이 보안에 취약해서 토큰 자체가 노출되면 어떻게 될까요?
- 산 영화 티켓의 정보가 노출되어서 다른 사람이 이 티켓으로 영화를 보려고 한다고 해봅시다.
- 토큰은 이미 발급되면 그 자체로 인증 수단이 되므로 서버는 토큰과 함께 들어온 요청이 토큰을 탈취한 사람의 요청인지 확인할 수 없다.
리프레시 토큰이 있다면?
- 토큰의 유효기간이 하루라면?
- 토큰의 유효기간이 짧으면 사용자 입장에서는 받은 토큰을 너무 짧은 시간만 활용할 수 있으니 불편합니다.
- 이러한 불편한 지점을 해결하기 위해 리프레시 토큰이 등장합니다.
- 리프레시 토큰은 액세스 토큰과 별개의 토큰입니다.
- 사용자를 인증하기 위한 용도가 아닌 액세스 토큰이 만료되었을 때
- 새로운 액세스 토큰을 발급하기 위해 사용한다.
- 액세스 토큰의 유효 기간은 짧게 설정하고
- 리프레시 토큰의 유효 기간은 길게 설정하면
- 공격자가 액세스 토큰을 탈취해도 몇 분 뒤에는 사용할 수 없는 토큰이 되므로 더 안전하다.
첫번째 요청
- 클라이언트가 서버에게 인증을 요청합니다.
- 서버는 클라이언트에서 전달한 정보를 바탕으로 인증 정보가 유효한지 확인한 뒤클라이언트는 전달받은 토큰을 저장합니다.
- 액세스 토큰과 리프레시 토큰을 만들어 클라이언트에게 전달합니다.
- 서버에서 생성한 리프레시 토큰은 DB에도 저장해둡니다.
2번재 요청
- 인증을 필요로 하는 API를 호출할 때 클라이언트에서 저장된 액세스 토큰과 함께 API를 요청합니다.
- 서버에서 전달받은 액세스 토큰이 유효한지 검사한 뒤에 유효하다면 클라이언트에서 요청한 내용을 처리합니다.
액세스 토큰이 만료
- 시간이 지나고 액세스 토큰이 만료된 뒤에 클라이언트에서 원하는 정보를 얻기 위해 서버에게 API 요청을 보냅니다.
- 서버에서 액세스 토큰이 유효한지 검사합니다.
- 만료된 토큰이면 유효하지 않기 때문에 토큰이 만료되었다는 에러를 전달합니다.
리프레시 + 다시요청
- 클라이언트에서는 이 응답을 받고 저장해둔 리프레시 토큰과 함께 새로운 액세스 토큰을 발급하는 요청을 전송합니다.
- 서버에서는 전달받은 리프레시 토큰이 유효한지
- DB에서 리프레시 토큰을 조회한 후 저장해둔 리프레시 토큰과 같은지 확인한다.
- 만약 유효한 리프레시 토큰이라면 새로운 액세스 토큰을 생성한 뒤 응답합니다.
그 이후에는 클라이언트는 4번과 같이 다시 API를 요청합니다.
'개인공부 > 스프링 부트 3 백엔드 개발자 되기' 카테고리의 다른 글
스프링 부트 - 8장 스프링 시큐리티로 로그인/로그아웃, 회원 가입 구현하기 (0) | 2023.07.05 |
---|---|
스프링 부트 - 6장 레벨2 스프링 부트 3로 블로그 제대로 만들기 (0) | 2023.07.04 |
스프링 부트 - 5장_데이터베이스 조작이 편해지는 ORM (0) | 2023.06.29 |
스프링 부트 - 4장_스프링부트3의 테스트 (0) | 2023.06.29 |
스프링 부트 - 3장_스프링부트3 구조 이해하기 (0) | 2023.06.25 |