JWT 토큰을 생성하는데 필요한 정보를 UserDetails에서 가져올 수 있기 때문에 JWT 토큰을 생성하는 JwtTokenProvider를 생성
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
private final Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class);
private final UserDetailsService userDetailsService;
@Value("${springboot.jwt.secret}")
private String secretKey = "secretKey";
private final long tokenValidMillisecond = 1000L * 60* 60;
@PostConstruct
protected void init(){
LOGGER.info("[init] JwtTokenProvider 내 secretKey 초기화 시작");
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
LOGGER.info("[init] JwtTokenProvider 내 SecretKey 초기화 완료");
}
public String createToken(String userUid, List<String> roles){
LOGGER.info("[createToken] 토큰 생성 시작");
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] 토큰 생성 완료");
return token;
}
public Authentication getAuthentication(String token){
LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 시작");
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUsername(token));
LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 완료, UserDetails UserName : {}",
userDetails.getUsername());
return new UsernamePasswordAuthenticationToken(userDetails,"", userDetails.getAuthorities());
}
public String getUsername(String token){
LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출");
String info = Jwts.parser().setSigningKey(secretKey).parseClaimsJwt(token).getBody()
.getSubject();
LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출 완료, info: {}", info);
return info;
}
public String resolveToken(HttpServletRequest request){
LOGGER.info("[resolveToken] HTTP 헤더에서 Token 값 추출");
return request.getHeader("X-AUTH-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){
LOGGER.info("[validateToken] 토큰 유효 체크 예외 발생");
return false;
}
}
}
우선 토큰 생성을 하기 위해 SecretKey 값을 정의 하였음
@Value 값은 properties 파일에서 정의 가능
spring.jwt.secret = flature!@#
@PostConstruct
protected void init(){
LOGGER.info("[init] JwtTokenProvider 내 secretKey 초기화 시작");
System.out.println(secretKey)
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
System.out.println(secretKey)
LOGGER.info("[init] JwtTokenProvider 내 SecretKey 초기화 완료");
}
@PostConstruct는 해당 객체가 빈 객체로 주입된 이후 수행되는 메서드를 가리킴
앞서 JwtTokenProvider 클래스 작성전 @Component를 지정하여 애플리케이션을 실행하면 빈으로 자동 주입
됨
-> 그때 @PostConstruct 지정돼 있는 init() 메서드가 자동으로 실행
init 메서드에서는 secretkey를 base64 형식으로 인코딩 하였음
인코딩 전 원본 문자열
flature!@#
Base64 인코딩 결과
ZmxhdHVyZSFAIw==
public String createToken(String userUid, List<String> roles){
LOGGER.info("[createToken] 토큰 생성 시작");
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] 토큰 생성 완료");
return token;
}
JWT 토큰에 값을 넣기 위해 Claims 객체 생성
setSubject() 메서드를 통해 sub 속성에 값을 추가하려면 User의 uid 값을 사용
그 다음에는 해당 토큰을 사용하는 사용자의 권한을 확인할 수 있는 role 값을 별개로 추가
Jwts.builder()를 사용해 토큰 생성
public Authentication getAuthentication(String token){
LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 시작");
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUsername(token));
LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 완료, UserDetails UserName : {}",
userDetails.getUsername());
return new UsernamePasswordAuthenticationToken(userDetails,"", userDetails.getAuthorities());
}
이 메서드는 필터에서 인증이 성공했을 때 SecurityContextHolder에 저장할 Authentication 생성하는 역할
Authentication을 구현하는 편한 방법은 UsernamePasswordAuthenticationToken를 사용하는 것
UsernamePasswordAuthenticationToken은 AbstractAuthenticationToken을 상속 받고 있는데 이는 Authentication< interface >
와 CredentialsContainer < interface >
의 구현체
이러한 토큰 클래스를 사용하려면 초기화를 위한 UserDetails가 필요
-> 여기선 UserDetailsService를 통해 가져옴
이때 사용한 Username은 밑과 같이 구현
public String getUsername(String token){
LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출");
String info = Jwts.parser().setSigningKey(secretKey).parseClaimsJwt(token).getBody()
.getSubject();
LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출 완료, info: {}", info);
return info;
}
Jwts.parser() 를 통해 secretkey를 설정하고 클레임을 추출하여 토큰을 생성할때 넣었던 sub 값을 추출
public String resolveToken(HttpServletRequest request){
LOGGER.info("[resolveToken] HTTP 헤더에서 Token 값 추출");
return request.getHeader("X-AUTH-TOKEN");
}
이는 HttpServletRequest를 파라미터로 받아 헤더 값으로 전달된 X-AUTH-TOKEN 값을 가져와 리턴
클라이언트가 헤더를 통해 애플리케이션 서버로 JWT 토큰 값을 전달해야 정상적인 추출이 가능
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){
LOGGER.info("[validateToken] 토큰 유효 체크 예외 발생");
return false;
}
}
이 메서드는 토큰을 전달받아 클레임의 유효기간을 체크하고 boolean 타입의 값을 리턴하는 역할