반응형
이번에는 인증에 많이 사용되는 Jwt 토큰에 대해서 정리해본다.
JWT
- Json Web Token의 줄임말로 RFC 7519에 명세되어 있는 국제 표준
- 통신 양자간의 정보를 JSON 형식으로 사용하여 안전하게 전송하기 위한 방법
- Jwt는 정보가 토큰 자체에 포함된(Self-Contained) 클레임(Claim) 기반 토큰이다
- Jwt는 인증(Authentication)과 인가(Authoriation)에 사용되는 것이 가장 일반적
- 인증 절차를 거쳐 서버에서 jwt 토큰을 발급해주면 클라이언트는 이를 잘 보관하다가 API 호출 시 서버에 jwt 토큰을 함께 제출하여 서버로부터 행위에 대해 인가받을 수 있다.
- 해시 혹은 비대칭키 방식을 사용하여 서명(Signature) 하기 때문에 무결성을 검증할 수 있다
- URL에 대해 안전한 문자열로 구성되어 있어 어떤 경로로든 전송할 수 있다
Jwt 토큰을 통해 사용자를 인증하고 권한을 인가한다는 것은 알겠다. 해시 혹은 비대칭키 방식을 통해 암호화하는 것도 알겠다 인증받은 사용자의 토큰을 탈취당하면 인가받은 행위들에 대해 탈취당하는 것이 아닌가라고 생각할 수 있다. 맞는 말이다. 어떻게 그런 취약점을 보완하는지도 알아보도록 하겠다!
서버 기반 인증 VS 토큰 기반 인증
- 서버 기반 인증
- 사용자가 성공적으로 로그인한 이후 서버에서 사용자에 대한 세션(Session)을 생성
- 사용자의 브라우저는 세션 ID를 저장하는 쿠키가 생성됨
- 서버는 세션 ID를 통해 사용자를 식별하고, 사용자에 대한 정보를 저장, 관리
- 서버 기반의 문제점
- 서버에서 사용자에 대한 정보를 가진다는 것은 서비스의 사용자가 증가하면 서버에 부하가 크다.
- H/W적인 부분에서 업그레이드 및 교체가 필요하며 이를 수직 확장(Scale Up)이라고 한다.
- 경제적 부담 + 한 대의 서버 운영으로 인한 단일 장애 지점을 갖는다. 이는 서버가 다운되면 모든 서비스가 다운된다는 의미
- 사용자 증가로 인한 부하로 여러 서버를 운용하는 방법이 있고 이를 수평 확장(Scale Out)이라고 한다.
- 수평 확장은 동일한 사양의 컴퓨터가 추가 되는 것으로 경제적 부담이 훨씬 적고 확장에 유연
- 여러대의 서버를 이용하면 데이터의 불일치 문제가 발생할 수 있다.
- 모든 서버가 메모리에 동일한 세션 정보를 갖고 있는 것이 아니다.
- 토큰 기반 인증
- 사용자의 정보를 서버에 저장하지 않는다.
- 유저가 성공적으로 로그인하면, 서버는 클라이언트로 토큰을 발급
- 클라이언트는 토큰을 받아 젖아하고, 서버에 요청할 때 HTTP header에 실어 함께 전송하면 서버는 이를 검증하고 유저를 인가한다.
- 서버는 발급과 검증 두가지 역할만 할 뿐 직접 정보를 갖고 있지 않다.
- 토큰은 세션과 달리 클라이언트에 정보가 저장되므로 노출되기 쉬워 민감한 정보를 담아서는 안된다.
- 토큰 기반 인증을 사용하면 통신하면서 발생하는 오버헤드를 감안해야 한다.
- Jwt 같은 경우 데이터를 직접 갖고 있는 클레임(Claim) 기반 토큰으로 토큰의 만료를 구현할 수 있다.
JWT의 구조
- 토큰은 헤더(Header), 페이로드(Payload), 서명(Signature) 세 부분으로 구성되어 있으며 점(.)으로 분리된다. 따라서 Jwt는 헤더. 페이로드. 서명의 형태
- 각 구성 요소를 한 줄의 String으로 나타내기 위해 Base64로 인코딩하며 url safe 형태이다.
- Header
- 헤더에는 일반적으로 토큰의 유형과 암호화 알고리즘 두 가지 정보를 담는다.
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("typ", "JWT");
headers.put("alg", "HS256");
- Payload
- 페이로드는 사용자의 정보 혹은 데이터 속성 등을 나타내는 클레임(Claim) 정보 단위로 구성
- 클레임은 3가지로 구분되는데 등록된 클레임 (Registered Claim), 공개 클레임 (Public Claim), 비공개 클레임 (Private Claim)으로 구성되며 나는 막 구분해서 사용하지 않았기에 궁금하면 검색해보도록 하자.. 등록된 클레임 정도는 알아두면 좋을 것 같다!
Map<String, Object> payloads = new HashMap<String, Object>();
payloads.put("id", id);
payloads.put("auth", authority);
Date exp = calendar.getTime(); // expiration time
- Signature
- 특정 암호화 알고리즘을 사용하여 Base64 인코딩된 헤더와 페이로드 그리고 비밀키를 이용하여 암호화한다.
- 비밀키는 공격자로 하여금 레인보우 테이블 기법의 해킹을 어렵게 하기위해 원문과 함께 비밀키를 더하여 해싱하는데 사용! (ex: HMAC(Keyed-hash Message Authentication Code))
- 이는 비밀 키만 공유된다면 다수의 서버에서 토큰 유효성 검증 가능
- 공유자원 불필요!! -> 서버 유지 보수 편리 확장성
- 이는 비밀 키만 공유된다면 다수의 서버에서 토큰 유효성 검증 가능
return Jwts.builder()
.setHeader(headers)
.setClaims(payloads)
.setSubject(type.isAccess() ? accessToken : refreshToken)
.setExpiration(exp)
.signWith(SignatureAlgorithm.HS256, key.getBytes())
.compact();
검증
- jwt.io에 접속하여 생성된 토큰과 비밀키를 입력하면 Header와 Payload에 값이 나타난다.
JWT 토큰을 Access 토큰으로 사용 시 문제점
- JWT는 세션과 달리 Stateless 하다 서버에서는 아무런 정보가 없고, 토큰 자체의 만료일까지 토큰이 가지고 있기 때문에 토큰이 탈취당하면 토큰의 주인 계정에 접근할 수 있을 것이다.
- 이미 발행된 토큰에 대한 제어를 할 수가 없다. -> 토큰이 탈취당한 걸 알아도 어떻게 처리하기 힘들다.
해결방안?
- Refresh Token을 같이 생성하여 Access Token의 만료일을 짧게 설정하고, 만료된 토큰에 대해서 Refresh 하는 방식의 로직
- 마찬가지로 Refresh Token이 탈취되면 같은 위험이 발생한다는 것.
- Refresh Token을 따로 저장을 해야 하기 때문에 리소스 사용 (ex: DB, Redis)
완벽한 인증방식은 없는 듯하다.. 더 좋은 방식을 찾고 계속 보완해나가는 게 맞는 것 같다.
이어서 Refresh Token의 활용방안과 한계에 대해서 정리해보도록 하겠습니다.!
반응형
'Spring' 카테고리의 다른 글
카카오 OAuth2.0 적용하기 - 1 (0) | 2023.01.09 |
---|---|
Spring Security 작동 원리 (0) | 2022.11.23 |
Refresh Token이란? (1) | 2022.11.19 |
Spring MVC Life cycle (0) | 2022.11.17 |
Spring이란? (0) | 2022.11.17 |