2024. 8. 6. 15:47ㆍ스프링/스프링부트
20204/08/06
지난 시간에 jwt 개념에 대해 알아보았습니다.
이번 시간에는 jwt를 실제로 어떻게 사용하는지 알아보겠습니다.
※ JWT 다루기
▶ JWT dependency 추가하기
- 먼저, build.gradle에 다음 dependency를 추가합니다.
// JWT
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
- application.properties파일에 다음 코드를 추가합니다.
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==
▶ JwtUtil 만들기
📌 Util 클래스란 특정 매개 변(파라미터)에 대한 작업을 수행하는 메서드들이 존재하는 클래스를 뜻함.
👉 쉽게 설명하자면, 다른 객체에 의존하지 않고 하나의 모듈로서 동작하는 클래스
- JWT 관련 기능들을 가진 JwtUtil이라는 클래스를 만들어 JWT 관련 기능을 수행시킬 예정
<JWT 관련 기능>
1. JWT 생성
2. 생성된 JWT를 Cookie에 저장
3. Cookie에 들어있던 JWT 토큰을 Substring
4. JWT 검증
5. JWT에서 사용자 정보 가져오기
▶ 토큰 생성에 필요한 데이터
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
▼ JWT 데이터
package com.sparta.springauth.jwt;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Base64;
@Component
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
}
● Base64로 Encode된 Secret Key를 properties에 작성해두고 @Value를 통해 가져온다.
● JWT를 생성할 때 가져온 Secret Key로 암호화한다.
○ 이때 Encode된 Secret Key를 Decode해서 사용한다.
○ Key는 Decode된 Secret Key를 담는 객체
○ @PostConstruct는 딱 한 번만 받아오면 되는 값을 사용 할 때마다 요청을 새로 호출하는 실수를 방지하기
위해 사용됨.
- JwtUtil 클래스의 생성자 호출 이후에 실행되어 Key 필드에 값을 주입해줌.
● 암호화 알고리즘은 HS256 알고리즘을 사용
● Bearer란 JWT 혹은 OAuth에 대한 토큰을 사용한다는 표시
● 로깅이란 애플리케이션이 동작하는 동안 프로젝트의 상태나 동작 정보를 시간순으로 기록하는 것을 의미
○ 여기서는 Logback 로깅 프레임워크를 사용해서 로깅을 진행
📌 사용자의 권한의 종류를 Enum을 사용해서 관리한다.
● JWT를 생성할 때 사용자의 정보로 해당 사용자의 권한을 넣어줄 때 사용한다.
public enum UserRoleEnum { USER(Authority.USER), // 사용자 권한 ADMIN(Authority.ADMIN); // 관리자 권한 private final String authority; UserRoleEnum(String authority) { this.authority = authority; } public String getAuthority() { return this.authority; } public static class Authority { public static final String USER = "ROLE_USER"; public static final String ADMIN = "ROLE_ADMIN"; } }
▶ 1.JWT 생성
// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
}
● JWT의 subject에 사용자의 식별값 즉, ID를 넣는다.
● JWT에 사용자의 권한 정보를 넣는다. key-value 형식으로 key 값을 통해 확인할 수 있다.
● 토큰 만료시간을 넣는다.(ms 기준)
● issuedAt에 발급일을 넣는다.
● signWith에 secretKey 값을 담고있는 key와 암호화 알고리즘을 값을 넣어준다.
○ ket와 암호화 알고리즘을 사용하여 JWT를 암호화다.
▶ 2.JWT Cookie에 저장
// JWT Cookie 에 저장
public void addJwtToCookie(String token, HttpServletResponse res) {
try {
token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage());
}
}
▶ 3.받아온 Cookie의 Value인 JWT 토큰 substring
// JWT 토큰 substring
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
logger.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
● StringUtils.hasText를 사용하여 공백, null을 확인하고 startsWith을 사용하여 토큰의 시작값이 Bearer이 맞는지 확인한다.
● 맞다면 순수 JWT를 반환하기 위해 substring을 사용하여 Bearer을 잘라낸다.
▶ 4.JWT 검증
// 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
● Jwts.parserBuilder() 를 사용하여 JWT를 파싱할 수 있다.
● JWT가 위변조되지 않았는지 secretKey(key)값을 넣어 확인한다.
▶ 5.JWT에서 사용자 정보 가져오기
// 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
● JWT의 구조 중 Payload 부분에는 토큰에 담긴 정보가 들어있다.
● 여기에 담긴 정보의 한 ‘조각’ 을 클레임(claim)이라 부르고, 이는 key-value 의 한 쌍으로 이뤄져있다.
○ 토큰에는 여러 개의 클레임을 넣을 수 있다.
● Jwts.parserBuilder() 와 secretKey를 사용하여 JWT의 Claims를 가져와 담겨 있는 사용자의 정보를 사용한다.
▶ JWT 테스트 해보기
@GetMapping("/create-jwt")
public String createJwt(HttpServletResponse res) {
// Jwt 생성
String token = jwtUtil.createToken("Karina", UserRoleEnum.USER);
// Jwt 쿠키 저장
jwtUtil.addJwtToCookie(token, res);
return "createJwt : " + token;
}
@GetMapping("/get-jwt")
public String getJwt(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue) {
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if(!jwtUtil.validateToken(token)){
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
// 사용자 username
String username = info.getSubject();
System.out.println("username = " + username);
// 사용자 권한
String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
System.out.println("authority = " + authority);
return "getJwt : " + username + ", " + authority;
}
이제 JWT를 사용할 수 있으신가요?
코드가 많아서 처음엔 어렵게 느껴지실 수도 있지만
자세히 들여다보면 충분히 이해할만한 내용이랍니다.
※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.
'스프링 > 스프링부트' 카테고리의 다른 글
스프링 부트 - JWT의 개념 (1) | 2024.07.31 |
---|