[TIL] Spring Advanced - JWT authentication and role management
JWT authentication flow in Spring, token creation and verification using JwtUtil, Bearer token processing, and permission management with UserRoleEnum
한국어 원문은 여기에서 볼 수 있습니다.
What I studied
Handling JWTs
Add JWT dependency
set jwt.secret.key to
application.properties
Creating a JWT Util
- Util class
- refers to a class that has methods that perform operations on specific parameters (parameters). → A class that operates as a module without depending on other objects
We plan to create a class called JwtUtil with JWT-related functions to perform JWT-related functions.
- JWT-related features
Create JWT
Save the generated JWT in Cookie
Substring the JWT token contained in the cookie
JWT verification
Get user information from JWT
Data required for token creation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// 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); }
Write the Secret Key encoded in Base64 in properties and retrieve it through @Value.
- Encrypt with the Secret Key taken when creating the JWT.
At this time, the encoded Secret Key is decoded and used.
Key is an object containing the decoded Secret Key.
@PostConstruct is used to prevent the mistake of calling a new request every time you use a value that only needs to be received once.
- Executes after calling the constructor of the JwtUtil class and injects the value into the Key field.
The encryption algorithm uses the HS256 algorithm.
- Bearer indicates that a JWT or OAuth token is used.
- What is Bearer
https://docs.tosspayments.com/resources/glossary/bearer-auth- Logging means recording project status or operation information in chronological order while the application is running. - We will proceed with logging using the Logback logging framework.
- What is Bearer
- Used to enter the user’s permissions using the user’s information when creating a JWT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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";
}
}
Code description
UserRoleEnumis a class that manages user permissions (Role) as an enum.By using enum, you can prevent typos by not having to write permission values directly as strings.
Currently, two permissions are defined:
USERandADMIN.
1
2
USER(Authority.USER),
ADMIN(Authority.ADMIN);
USERis a normal user privilege.ADMINhas administrator privileges.The
authorityfield stores the permission string used by actual Spring Security.
1
private final String authority;
- Spring Security typically uses permission values with the
ROLE_prefix.
1
2
ROLE_USER
ROLE_ADMIN
- Store permission strings through the enum constructor.
1
2
3
UserRoleEnum(String authority) {
this.authority = authority;
}
- The permission string passed when creating an enum is stored in the
authorityfield.
1
2
USER("ROLE_USER")
ADMIN("ROLE_ADMIN")
- The
getAuthority()method returns the stored permission string.
1
2
3
public String getAuthority() {
return this.authority;
}
- Example
1
UserRoleEnum.USER.getAuthority()
1
ROLE_USER
- The
Authorityinner class manages permission string constants.
1
2
3
4
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
If you manage the permission string as a constant, you do not need to write the string yourself.
Prevent typos and improve maintainability.
1
"ROLE_USRE" // 오타 발생 가능
full flow
enum name
1
UserRoleEnum.USER
→ Permission types used in code
- Actual permission string
1
UserRoleEnum.USER.getAuthority()
→ Permission values recognized by Spring Security
1
ROLE_USER
- Clean up
UserRoleEnumManage user permission types with enums
AuthorityManage actual permission string constants
getAuthority()Returns a permission string to be used in Spring Security
- Purpose
- This is to manage permissions safely and consistently.
Create JWT
1 2 3 4 5 6 7 8 9 10 11 12 13
// 토큰 생성 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(); }
Insert the user’s identification value, i.e. ID, into the subject of the JWT.
Insert the user’s authorization information into the JWT. You can check it through the key value in key-value format.
Enter the token expiration time. It is based on ms.
Insert the date of issue into issuedAt.
Enter the key containing the secretKey value and the encryption algorithm in signWith.
- Encrypt JWT using ket and encryption algorithm.
Save to JWT Cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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()); } }
JWT token substring, which is the value of the received cookie
1 2 3 4 5 6 7 8
// 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"); }
- Use StringUtils.hasText to check for blank and null, and startsWith to check if the starting value of the token is Bearer.- If correct, truncate the Bearer using substring to return a pure JWT.
JWT verification
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 토큰 검증 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; }
You can use
Jwts.parserBuilder()to parse JWT.Check that the JWT has not been forged or altered by entering the secretKey (key) value.
Get user information from JWT
1 2 3 4
// 토큰에서 사용자 정보 가져오기 public Claims getUserInfoFromToken(String token) { return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); }
The Payload part of the JWT structure contains information contained in the token.
A ‘piece’ of information contained here is called a claim (claim), and it consists of a key-value pair. A token can contain multiple claims.
Use
Jwts.parserBuilder()and secretKey to retrieve JWT Claims and use the user information contained therein.