JwtTokenProvider 구현
- JWT 기반 보안 시스템을 구현하기 위해 JWT 토큰을 생성하는 메서드가 필요하다.
- 이를 위해 JwtTokenProvider라는 JWT 토큰을 생성하는 메서드를 단계 별로 구현하겠다.
1. secretKey 생성
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
private final Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class);
private String secretKey = "secretKey";
private final long tokenValidMillisecond = 1000L * 60 * 60; // Valid 1 hours
@PostConstruct
protected void init() {
LOGGER.info("[init] JwtTokenProvider: Start init secretKey");
System.out.println(secretKey);
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
System.out.println(secretKey);
LOGGER.info("[init] JwtTokenProvider: Finish init secretKey");
}
- 위 코드에서
init()
메서드는 토큰을 생성하기 위한secretKey
를 정의한다. 이 때 Base64 형식으로 인코딩한다. - 참고로 Base64 인코딩이란 이진 데이터를 텍스트 기반 시스템에서 사용할 수 있도록 변환 (예: 이메일, HTTP, HTML)하며, 데이터 손실 없이 안전하게 전송 및 저장하도록 해준다.
@PostConstruct
어노테이션은 해당 객체가 Bean 객체로 주입된 이후 수행되는 메서드를 가리킨다.JwtTokenProvider
클래스는@Component
어노테이션이 지정돼 있어 애플리케이션이 시작되면 Bean으로 자동 주입되는데, 그때@PostConstruct
어노테이션이 지정된init()
함수가 실행된다.
2. Token 생성 메서드 구현
public String createToken(String userUid, List<String> roles) {
LOGGER.info("[createToken] Start to create Token");
Claims claims = Jwts.claims().setSubject(userUid);
claims.put("roles", roles);
Date now = new Date();
String token = Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenValidMillisecond))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
LOGGER.info("[createToken] Complete to create Token");
return token;
}
Claims
객체는 토큰에 값을 삽입하기 위해 필요한 객체이다.setSubject
메서드를 통해 sub 속성에 값은 User의 uid 값을 사용한다.- 위 코드에서는 "roles"라는 사용자의 권한을 확인할 수 있는 값을 따로 추가했다.
Jwts.builder()
메서드를 통해 token을 생성했다.signWith
는 암호화 알고리즘을 정의하며, secret key 값을 세팅한다.
Claims
- Spring Security에서 클레임 객체는 인증된 사용자 또는 요청에 대한 추가 정보를 담는 데이터 구조이다.
- 리소스에 대한 접근 권한을 승인하고, 요청 처리 방법에 대한 결정을 내리고, 사용자 또는 요청에 대한 추가적 컨텍스트를 제공하는 데 사용될 수 있다.
주요 Claim 타입:
- Subject: Claim의 사용자 또는 ID
- Issuer: Claim을 발급한 엔티티
- Audience: Claim의 수신자
- Expiration Time: Claim이 . 더 이상 유효하지 않은 시간
- Roles: 유저 또는 엔티티의 역할
- Permissions: 유저 또는 엔티티에게 부여된 권한
3. 인증을 생성하는 메서드 구현
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "",
userDetails.getAuthorities());
}
- 필터에서 인증이 성공했을 때
SecurityContextHolder
에 저장하는Authentication
을 생성하는 메서드이다. Authentication
을 구현하는 편한 방법은UsernamePasswordAuthenticationToken
을 사용하는 것이다.- 위 코드에서 UserDetails는 SpringSecurity에서 제공하는 인터페이스이며, 이 링크에서 구현 예제를 확인할 수 있다.
- UserDetails의 username은 각 사용자를 구분할 수 있는 ID를 의미한다.
UsernamePasswordAuthenticationToken의 상속 구조는 다음과 같다.
4. Token에서 회원 정보 추출하는 메서드 구현
public String getUsername(String token) {
String info = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody()
.getSubject();
return info;
}
Jwts.parser()
를 통해 secretKey를 설정하고, Claim을 추출해서 토큰을 생성할 때 넣었던 sub 값을 추출한다.
5. HTTP 헤더에서 Token 값 추출 메서드 구현
public String resolveToken(HttpServletRequest request) {
return request.getHeader("X-AUTH-TOKEN");
}
- 이 메서드는
HttpServletRequest
를 파라미터로 받아 헤더 값으로 전달된 'X-AUTH-TOKEN' 값을 가져와 리턴한다. - 클라이언트가 헤더를 통해 애플리케이션 서버로 JWT 토큰 값을 전달해야 정상적인 추철이 가능하다.
- 헤더의 이름은 임의로 변경할 수 있다.
6. Token의 유효성 검사 메서드 구현
public boolean validateToken(String token) {
LOGGER.info("[validateToken] 토큰 유효 체크 시작");
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
- 위 메서드에서는 Token을 전달받아 Claim의 유효기간을 체크하고 boolean 타입 값을 리턴하는 역할을 한다.
전체 코드 링크:
GITHUB
참고
- Google Gemini
- 스프링 부트 핵심 가이드 "스프링 부트를 활용한 애플리케이션 개발 실무" , 장정우, 2022
- https://github.com/wikibook/springboot/tree/main/chapter13_security
'Development > Spring' 카테고리의 다른 글
[Spring] Spring Security + JWT: SecurityFilterChain, AccessDeniedHandler, AuthenticationEntryPoint (0) | 2024.04.07 |
---|---|
[Spring] Spring Security + JWT: JwtAuthenticationFilter (0) | 2024.04.06 |
[Spring] JWT와 Spring Security (1) | 2024.03.27 |
[Spring] 인증과 권한 부여, Spring Security (0) | 2024.03.22 |
[Spring] 서버 간 통신하기: WebClient (0) | 2024.03.20 |