HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🧚
[1기]최종 프로젝트 데브코스
/
📜
[팀13] 사각사각 ✏️
/
🎊
기술 문서
/
🔑
JWT
🔑

JWT

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 으로 받도록 처리하다.
HTTP 인증 - HTTP | MDN
HTTP는 액세스 제어와 인증을 위한 프레임워크를 제공합니다. 가장 일반적인 인증 방식은 "Basic" 인증 방식입니다. 이 페이지에서는 일반적인 HTTP 인증 프레임워크를 소개하고 서버에 HTTP의 Basic 인증 방식으로 접근을 제한하는 것을 보여 줍니다. RFC 7235는 서버에 의해 클라이언트 요청을 시도( challenge (en-US) )하고, 클라이언트에 의해 인증 정보를 제공하기 위해 사용될 수 있는 HTTP 인증 프레임워크를 정의합니다.
HTTP 인증 - HTTP | MDN
https://developer.mozilla.org/ko/docs/Web/HTTP/Authentication
HTTP 인증 - HTTP | MDN
 
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); }