JWT란?
토큰 인증 방식으로 기존 세션 인증방식과는 달리 stateless 한 인증 방식이다.
기본적으로 JWT 라이브러리가 다양하게 존재하는데, JAVA에서는 Java-jwt 와 jjwt 라이브러리를 많이 사용한다.
(개인적으로 Auth0에서 개발한 java-jwt가 직관적이여서 사용하기 편하다고 느껴진다.)
자세한 내용은 아래 블로그를 참조
JWT는 선택적 서명과 선택적 암호화를 제공한다라는 말이 존재한다.
JWT 토큰을 생성하기 위해서 서명과 암호화가 필수는 아니라는 뜻이다.
JWT에서 서명을 한상태를 = JWS
JWT에서 서명과 암호화를 한 상태 = JWE
우리가 일반적으로 말하는 JWT가 JWS인 상태를 의미한다.
하지만 서명은 JWT 토큰을 생성하기 위해 필수가 아니더라도,
무결성을 보장하고 토큰 변조를 막기 위해서는 반드시 서명을 해야된다.
JWT는 서명을 통해서 무결성을 보장하고 인증을 증명할 수 있지만, 기밀성을 보장하지는 않는다.
(고로 중요한 데이터는 암호화를 진행해야된다.)
JWT는 세가지 세그먼트로 구성되어 있다. ( Header , Payload , Signature)
string 문자열로 구현되어있으며, '.' 으로 문자열에서 구분값을 나타내고 있다.
ex) eyJ0eXAiOiJ0ZXN0MiIsImFsZyI6IkhTMjU2In0. ===> header
eyJzaWduX2NoZWNrIjoiMSIsInN1YiI6InRlc3QifQ. ===> payload
O9nMVtwZfR-Y-8b_0E6oBw-oqR9S3I_DU5cWfq-ABO8 ===> Signature
JWT - Header
헤더는 알고리즘 방식과 타입을 지정할 수 있다.
jwt 생성할때 헤더를 지정하지 않아도, jwt에서 기본 지정 옵션값으로 헤더를 지정해서 생성한다.
기본 옵션값은 아래와 같다.
(algorithm : HS256
typ : JWT)
그로 인해 추가적인 커스터마이징이나 특정한 설정이 필요한 경우에만 개발자가 명시적으로 헤더를 조작한다.
HS256을 사용할거면 굳이 설정을 안해도 돼서 편리하다고 생각된다.
지정하고 싶으면 Map을 구현하여 알고리즘 방식과 타입을 지정해서 header를 등록하면 된다.
Map<String, Object> header = new HashMap<>();
header.put("alg","HS512");
header.put("typ","JWT");
String token = JWT.create().withHeader(header)
근데 typ을 'JWT'로 명시하는게 기본 규격인데, 'JWT' 인 값으로 안하고 다른 문자열로 지정해도 토큰값은 정상적으로 생성이 되었다. (생성하는데 큰 문제는 없는듯??? 하지만 기본 규격을 지키자 ㅎ)
JWT - Payload
서버와 클라이언트가 주고 받는 data 의 집합으로 단일 data에 대해서는 '클레임' 으로 정의 되어 있다.
클레임은 3가지의 유형으로 구분된다.
1. 등록된 클레임
name과 뜻이 정의 되어 있어서 지정된 네이밍을 사용하면 됨.
네이밍 길이는 간결하게 하기위하여 length 3으로 통일됨
iss: 토큰 발급자(issuer)
sub: 토큰 제목(subject)
aud: 토큰 대상자(audience)
exp: 토큰 만료 시간(expiration)
nbf: 토큰 활성 날짜(not before)
iat: 토큰 발급 시간(issued at)
jti: JWT 토큰 식별자(JWT ID) 중복 방지를 위해 사용하며 일회용 토큰(Access Token) 등에 사용
2. 공개된 클레임
등록되어 있지 않고, 개인이 만든 클레임으로 , 여러시스템의 중복과 충돌을 막기 위하여 url 형식으로 등록하는것을 권장한다.
예시로 아래와 같이 url을 구성한다.
- https://devnoong.tistory.com//claims/user_role: 특정 웹사이트에서 정의한 사용자 역할 클레임
- https://devnoong.tistory.com//claims/subscription_level: 다른 웹사이트에서 정의한 구독 수준 클레임
이렇게 url로 지정 하면 각각의 클레임이 고유하게 식별될 수 있고, 서로 다른 조직에서 정의한 클레임이 충돌할 염려가 없다.
String token = JWT.create()
.withClaim("https://devnoong.tistory.com//claims/user_role", "admin")
.withClaim("https://devnoong.tistory.com//claims/subscription_level", "premium")
3. 비공개 클레임
특정 사용자와 협의 된 클레임이다.
ex) "token_type": access
이렇게 생성된 클레임들은 base64로 인코딩된 값으로 누구나 decode가 가능하기때문에 민감한 정보는 담지 말아야한다.
===> 기밀성을 보장하지 않음. 민감한 정보는 암호화해서 클레임으로 등록해야된다.
JWT - Signature
서명(Signature)은 토큰의 무결성을 검증할 때 사용된다.
서명을 생성하는 방법은 아래와 같다
① 헤더 + payload 값을 결합해서 base 64로 인코딩한다.
② 위에서 인코딩 된 값을 서버의 개인키를 이용한 알고리즘을 사용하여 해싱 처리
③ 해싱된 값을 다시 base64로 인코딩 해서 서명을 생성 한다.(sign 메서드 사용)
String token = JWT.create()
.withHeader(header)
.withSubject("test")
.withClaim("sign_check","1")
.sign(algorithm);
즉 , payload의 클레임값이 변경되면 서명값도 변경이된다.
JWT - 검증
이렇게 토큰이 생성되면 토큰을 검증 한 후에, 유효한 토큰일 경우 decoder를 통해 payload 클레임 데이터를 확인 할 수 있다.
String secret = "secrect-key";
// 256 비트이상으로 지정하지 않아도, jwt 라이브러리에서 적절한 크기로 자동 변환해줌.
Algorithm algorithm = Algorithm.HMAC256(secret);
byte[] keyBytes = secret.getBytes();
// 최소 256 비트 이상, 보안 정책에 따라 특정 길이의 키를 사용해야 하는 경우에 유용하다.
// Key key = Keys.hmacShaKeyFor(keyBytes);
// Verify the token
JWTVerifier verifier = JWT.require(algorithm)
.build();
// Decode and verify the token
DecodedJWT decodedJWT = verifier.verify(token);
// Print the claims
System.out.println("Decoded Token:");
System.out.println("User Role: " + decodedJWT.getClaim("https://devnoong.tistory.com//claims/user_role").asString());
System.out.println("Subscription Level: " + decodedJWT.getClaim("https://devnoong.tistory.com//claims/subscription_level").asString());
'WEB > BACKEND' 카테고리의 다른 글
[Postman ] 400 Bad Request ERROR 발생 (비정상적 공백 ) (0) | 2024.08.21 |
---|---|
[SpringBatch] 성능 속도 개선 확인해보기 (0) | 2023.12.14 |
[Spring Batch] Spring Batch 개념 정리 (0) | 2023.12.07 |
[Nginx] AWS EC2(Cloud9) Nginx 설치하기 (0) | 2023.04.19 |
[Nginx] Nginx 개념 (0) | 2023.04.07 |