배경
HTTP 프로토콜 통신은 모든 응답이 완료되면 끊어진 상태과 되는 비연결성과, 클라이언트 측의 상태를 보관하지 않는 무상태성(Stateless)의 특징을 가진다.
그러나 클라이언트의 정보가 계속해서 쓰여야하는 경우가 있다. 예를 들어 로그인을 진행한 후 다른 서비스를 진행할 때, 로그인 정보가 어딘가에 담겨있지 않다면 매번 로그인을 해야 한다.
그래서 기존에는 쿠키와 세션을 가지고 아래와 같은 과정을 통해 사용자를 확인하였다.
쿠키와 세션을 이용한 사용자 인증 동작 순서
클라이언트가 ID, PW로 서버에 로그인 요청을 한다.
서버에서는 ID, PW로 인증하고, 사용자를 식별할 세션 ID를 만들어서 서버의 세션 저장소에 저장한다.
세션 ID를 특정한 형태(쿠키 혹은 JSON)로 클라이언트에 다시 반환한다.
이후 클라이언트는 인증이 필요한 API를 요청할 때마다 세션 ID를 헤더 쿠키에 담아 서버에 전달한다.
서버에서는 인증이 필요한 API일 경우, 세션 ID가 세션 저장소에 있는지 확인한다.
있다면 로그인된 사용자로 식별하고 응답을 한다.
브라우저 종료 시 쿠키에서 세션 ID를 제거하고 서버에서도 제거된다.
한계
이처럼 쿠키와 세션을 이용한 사용자 인증은 클라이언트의 정보가 서버에서 관리되고 있으므로 Stateful
하다.
Statusful의 단점으로는 인증된 사용자가 많아지면 서버의 메모리 및 리소스를 많이 사용하게 된다. 또한 인증에 대한 정보를 여러 서비스에서 사용하는 경우 정보를 공유하거나 복제하는 처리가 필요해서 확장성이 저하될 수 있다.
그래서 토큰을 이용한 사용자 인증 방식이 나타나게 되었다.
토큰을 이용한 사용자 인증 동작
- 클라이언트가 ID, PW로 서버에 로그인 요청을 한다.
- 서버에서는 ID, PW로 인증하고, 사용자를 식별할 토큰을 발급하고 헤더를 통해 전달한다.
- 클라이언트는 이 토큰을 가지고 있으면서, 매번 API를 요청할 때마다 토큰을 서버에게 전달한다.
- 서버는 인증이 필요한 API일 경우, 본인들이 발급한 토큰이 맞는지, 만료되지 않았는지 등을 따져서 유효성을 검사하고, 유효한 토큰일 경우 사용자로 식별하고 응답을 한다.
토큰 방식은 클라이언트의 정보를 사용자 측에서 관리하고 있기 때문에 Stateless
하다.
JWT(JSON Web Token)
여기서 대체로 많이 쓰이는 토큰은 JWT이다.
웹에서 사용하고 JSON 형식으로 정보를 담고 있는 토큰이다.
JWT 구조
Header, Payload, Signature 순으로 구성되어 있다.
- Header
- 토큰이 어떤 알고리즘으로 암호화 되어 있는지에 대한 정보를 담고 있다.
- Payload
- 토큰이 담고자하는 정보들을 가지고 있다.
- Signature
- Header에 나타나있는 방식을 통해 유효한 토큰인지 확인할 수 있는 암호화된 인증 정보를 담고 있다.
JWT를 이용한 사용자 인증 동작
Access Token과 Refresh Token
일반적으로 서버가 ID, PW로 사용자가 확인이 되었을 때 Access Token을 발급해준다. Access Token은 발급되고 나면 토큰을 가지고 있는 사람은 누구나 권한 접근이 가능해진다. 그러므로 Access Token의 유효시간은 짧게 가져가야 한다.
하지만 로그인 유지를 위해서는 유효시간이 긴 토큰이 필요하다. 이 때 사용하는 것이 Refresh Token이다.
로그인 및 토큰 발급 방법
- 로그인 성공 시 서버에서 AccessToken(ATK), Refresh Token(RTK)을 응답해준다.
- 클라이언트는 발급받은 ATK, RTK를 로컬 스토리지에 저장한다.
- 클라이언트는 요청을 할 때마다 HTTP 헤더의 Authorization 속성으로 토큰을 전달한다.
- 서버에서는 HTTP 헤더의 Authorization 값을 이용해서 사용자를 조회해온다. 토큰의 payload에서 type을 통해 ATK인지 RTK인지 구분한다.
- Interceptor 혹은 AOP 이용
- ATK인 경우 해당 요청의 결과를 리턴하고, RTK인 경우 재발급 API를 호출하면 ATK를 재발급해주고, 그 외의 API인 경우에는 접근할 수 없다.
구현
구현할 때 아래 URL들을 참고해서 구현하였다.
출처
- https://80000coding.oopy.io/9a56cae9-8626-4025-b3f9-c02251980d0f
- https://ocblog.tistory.com/56
- https://sol-devlog.tistory.com/22