2023. 12. 18. 04:56ㆍ카테고리 없음
JWT의 기본골자는 3단계로 나눠져 있다.
- Header : 토큰의 유형 및 사용하는 해시 알고리즘과 같은 메타데이터에 대한 정보를 포함한다.
- Payload : 토큰에 포함되는 클레임 정보를 담고 있다. 클레임은 사용자에 대한 정보나 추가적인 데이터를 나타낸다.
- Signature : Header와 Payload를 조합하고, 이를 비밀 키(secret key)를 사용하여 서명한 결과물입니다. 서버는 이 서명을 통해 토큰이 유효한지 확인할 수 있다.
또한 jwt를 사용하는 웹은 유틸리티 클래스와 필터클래스로 나뉘어져있는게 일반적인 구성이다.
- JWT Util 클래스:
- JWT의 생성, 검증 및 추출과 같은 기본적인 작업을 처리한다.
- 주로 토큰의 생성, 검증, 정보 추출 등의 메서드를 제공한다.
- 서비스나 컨트롤러에서 이 유틸리티 클래스를 사용하여 토큰을 생성하거나 검증한다.
- JWT Filter 클래스:
- Spring Security나 다른 웹 프레임워크에서 제공하는 필터를 이용하여 구현될 수 있다.
- HTTP 요청을 가지고 헤더에서 JWT를 추출하고, 추출한 토큰을 유효성 검증한다.
- 유효한 토큰이 있는 경우, 사용자의 인증 상태를 설정하고 요청을 계속 진행하여. 정상적이지 않은 경우, 인증 실패로 처리하거나 다른 처리를 수행한다.
JWT UTIL 클래스는 다음과같은 세부 코드를 가진다.
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String BEARER_PREFIX = "Bearer ";
헤더의 키와 토큰의 식별자에 대한 상수를 정의하고
@Value("${jwt.secret.key}")
private String secretKey;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
private Key key;
사용되는 필드를 선언해준다.
- secretKey: application.properties에 주입되는 JWT 서명에 사용되는 비밀 키이다.
- signatureAlgorithm: 사용할 서명 알고리즘으로 HS256을 사용하고 있다. 이는 변경가능하다.
- key:사용될 Key이다.
.
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
bean이 생성된후 초기화를 시킨다. 시크릿 키를 Base64기반으로 디코딩해서 초기화한다.
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
resolveToken: Http요청에서 토큰을 추출하는 메서드이다.
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
log.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
validateToken: 토큰의 유효성을 검증하고, 문제가 있으면 예외처리를 통해 로그에 기록한다.
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
getUserInfoFromToken: 토큰에서 사용자 정보를 추출한다.
이상이 Http요청에서 토큰을 받아와 유효성을 검사하는 메서드들이다.
다음은 토큰을 생성하는 메서드이다.
public String createToken(String username) {
Date date = new Date();
// 토큰 만료시간 60분
long TOKEN_TIME = 60 * 60 * 1000;
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username)
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
createToken:주어진 사용자이름에 대한 토큰을 생성하여 인증및 권한및 권한을 부여해주는 메서드이다.
이상이 JwtUtil 클래스가 가지는 세부적인 구조기능과 사용되는 메서드들이다.
다음은 JwtFilter 기능을 세세하게 살펴보자!
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = jwtUtil.resolveToken(request);
if(Objects.nonNull(token)) {
if(jwtUtil.validateToken(token)) {
Claims info = jwtUtil.getUserInfoFromToken(token);
String username = info.getSubject();
SecurityContext context = SecurityContextHolder.createEmptyContext();
UserDetailsImpl userDetails = userDetailsService.getUserDetails(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
} else {
CommonResponseDTO commonResponseDto = new CommonResponseDTO("토큰이 유효하지 않습니다.", HttpStatus.BAD_REQUEST.value());
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType("application/json; charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(commonResponseDto));
return;
}
}
filterChain.doFilter(request, response);
}
위 필터코드는 Spring Security를 이용해 JWT토큰 유효성을 검사하고 해당 토큰의 정보를 가져와 SecurityContextHolder에 저장하는 클래스이다.
JwtAuthorizationFilter클래스는 가져온 토큰이 유효하면, 해당 토큰으로부터 추출한 사용자 이름을 기반으로 UserDetailsService에서 getUserDetails 메서드를 통해 사용자 정보를 가져온다.
가져온 사용자 정보를 Spring Security의 SecurityContextHolder를 설정하여 현재 사용자를 인증해준다.
만약 토큰이 유효하지 않으면 클라이언트에 오류를 Response에 담아 보낸다.
이 과정을 통해 JWT 토큰의 유효성검사를 진행하고 최종적으로 Sercurity에 Http에서 받아온 사용자를 설정해주는 역할을 수행하는것이다.
까먹으면 다시와서 보려고 좀 세세하게 정리해보았다..