JWT (Json Web Token)
JWT는 Stateless 상태를 유지하며, 서버에서 사용자를 식별할 수 있는 수단을 제공한다.
- 서버에서 사용자가 성공적으로 로그인을 하면 JWT를 반환해준다.
- 클라이언트는 JWT를 로컬 영역에 저장하고, 이후 서버에 요청을 보낼 때 JWT를 HTTP 헤더에 Bearer Authentication Token을 포함시킨다.
- 서버는 클라이언트가 전달한 JWT를 통해 사용자를 식별 한다.
Bearer
JWT 혹은 OAuth에 대한 토큰을 사용한다. (RFC 6750)
JWT 의존성 추가 및 설정
implementation 'com.auth0:java-jwt:3.18.2'
application.yml JWT 관련 설정
security: jwt: header: authorization issuer: monthsub client-secret: ${JWT_CLIENT_SECRET} expiry-seconds: 86400
jwt 토큰 방식을 header 에 toekn (커스텀 토큰) 방식 아닌 authorization 으로 받도록 처리하다.
Config > Security
@Component @Getter @Setter @ConfigurationProperties(prefix = "security") public class Security { private Jwt jwt; @Getter @Setter public static class Jwt { private String header; private String issuer; private String clientSecret; private int expirySeconds; } }
@ConfigurationProperties(prefix = "security") 쓰기 위해 의존성 추가
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
JWT 클래스
- JWT 발행을 위한 sign 메소드
- JWT 발행을 위한 verify 메소드
public String sign(Claims claims) { Date now = new Date(); JWTCreator.Builder builder = create(); builder.withIssuer(issuer); builder.withIssuedAt(now); if (expirySeconds > 0) { builder.withExpiresAt(new Date(now.getTime() + expirySeconds * 1_000L)); } builder.withClaim("username", claims.username); builder.withArrayClaim("roles", claims.roles); return builder.sign(algorithm); } public Claims verify(String token) throws JWTVerificationException { return new Claims(jwtVerifier.verify(token)); }
JwtAuthentication 클래스
- 인증 완료 후 token, userId, useranme을 담기 위함.
public class JwtAuthentication { public final String token; public final Long userId; public final String username; JwtAuthentication( String token, Long userId, String username ) { this.token = token; this.userId = userId; this.username = username; } }
JwtAuthentiactionToken 클래스
- pricipal은 JwtAuthentication
public class JwtAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; private String credentials; public JwtAuthenticationToken(String principal, String credentials) { super(null); super.setAuthenticated(false); this.principal = principal; this.credentials = credentials; } JwtAuthenticationToken(Object principal, String credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); super.setAuthenticated(true); this.principal = principal; this.credentials = credentials; } // ... 생략 }
JwtAuthenticationProvider 클래스
- JwtAuthenticationToken 타입을 처리할 수 있는 AuthenticationProvider 인터페이스 구현체를 추가
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { JwtAuthenticationToken jwtAuthentication = (JwtAuthenticationToken) authentication; return processUserAuthentication( String.valueOf(jwtAuthentication.getPrincipal()), jwtAuthentication.getCredentials() ); } private Authentication processUserAuthentication( String principal, String credentials ) { try { User user = userService.login(principal, credentials); List<GrantedAuthority> authorities = user.getPart().getAuthorities(); String token = getToken(user.getUsername(), authorities); // 토큰을 만든다 JwtAuthentication jwtAuthenticationToken = new JwtAuthentication( token, user.getId(), user.getUsername() ); JwtAuthenticationToken authenticated = new JwtAuthenticationToken( jwtAuthenticationToken, null, authorities ); authenticated.setDetails(user); return authenticated; } catch (IllegalArgumentException e) { throw new BadCredentialsException(e.getMessage()); } }
JwtAuthenticationFilter 클래스
- 헤더에서 토큰을 가져온 다음 JWT 토큰을 검증하고, 디코딩한 다음 JwtAuthenticationToken 객체를 생성
- principal 필드 — JwtAuthentication 객체
- details 필드 — WebAuthenticationDetails 객체 (클라이언트 IP 정보를 지니고 있음)
private void authenticate( HttpServletRequest request, String token ) { Jwt.Claims claims = verify(token); log.debug("Jwt parse result: {}", claims); String username = claims.username; List<GrantedAuthority> authorities = getAuthorities(claims); if (isEmpty(username) || authorities.size() <= 0) { return; } User user = this.authenticationService.findByUserName(username); JwtAuthenticationToken jwtToken = new JwtAuthenticationToken( new JwtAuthentication(token, user.getId(), username), null, authorities ); log.info("userId: {}", user.getId()); jwtToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(jwtToken); } private String getToken(HttpServletRequest request) { String token = request.getHeader(headerKey); String decodedToken = null; if (isEmpty(token)) { return null; } log.debug("Jwt token detected: {}", token); if (!token.startsWith(this.BEARER)) { throw new UnAuthorize(); } try { decodedToken = URLDecoder.decode(token.replace(BEARER + " ", ""), "UTF-8"); } catch (Exception e) { log.error(e.getMessage(), e); } return decodedToken; } private Jwt.Claims verify(String token) { return jwt.verify(token); }