Jwt 토큰을 검증하는 코드를 만들어 보도록 하겠습니다.
먼저 application.yml에 다음 의존성을 추가해 줍니다.
jwt:
header: Authorization
secrete : ZXJkZ3RmaHlqdWlqbG9pb3V0eXJldHdyZWZzZGdnaGpsaW84NzY3NTY0NXllcnNnZmRnbmhtaixobGlvN2k2dTV5ZXRyZ3NkZmJuY2dtaGcsamx1eWlvN2l0NnU1eWU0dHdlYWY=
access-token-validity-in-seconds: 360000
jwt토큰을 인코딩할 비밀키와 access-token 유효 기간을 등록해줬습니다.
이 값을 사용하기 위해 Properties를 추가해줍니다.
JwtProperties
package com.jwt.domain.login.jwt;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
private String header;
private String secret;
private Long accessTokenValidityInSeconds;
}
JwtAccessDeniedHandler를 만들어줍니다. 이 핸들러는 Jwt의 인가과정에서의 예외를 처리해 줍니다.
JwtAccessDeniedHandler
package com.jwt.domain.login.jwt;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "FORBIDDEN");
}
}
상태 코드는 다음과 같이 만들어 줍니다.
ResponseStatusCode
package com.jwt.web.json;
public class ResponseStatusCode {
public static final int OK = 200;
public static final int URL_NOT_FOUND = 404;
public static final int EMAIL_NOT_VERIFIED = 410;
public static final int WRONG_PARAMETER = 420;
public static final int LOGIN_FAILED = 430;
public static final int SERVER_ERROR = 500;
// 토큰 응답 코드
public static final int TOKEN_EXPIRED = 4011;
public static final int TOKEN_IS_BLACKLIST = 4012;
public static final int TOKEN_WRONG_SIGNATURE = 4013;
public static final int TOKEN_HASH_NOT_SUPPORTED = 4014;
public static final int NO_AUTH_HEADER = 4015;
public static final int TOKEN_VALIDATION_TRY_FAILED = 4016;
private ResponseStatusCode() {
}
}
응답 객체를 만들어보겠습니다.
ApiResponseJson
package com.jwt.web.json;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.http.HttpStatus;
@Getter
@ToString
@NoArgsConstructor
public class ApiResponseJson {
public HttpStatus httpStatus;
public int code;
public Object data;
// 오류시 사용
public ApiResponseJson(HttpStatus httpStatus, int code, Object data) {
this.httpStatus = httpStatus;
this.code = code;
this.data = data;
}
//정상 동작시 사용
public ApiResponseJson(HttpStatus httpStatus, Object data) {
this.httpStatus = httpStatus;
this.code = ResponseStatusCode.OK;
this.data = data;
}
}
Client가 받는 요청의 형식을 어느정도 통일해 줍니다.
이제 Jwt검증 과정에서 인증 예외가 발생하면 AuthenticationEntryPoint
의 commence()
함수가 호출됩니다. 인증 예외가 발생한 이유를 알려주도록 하겠습니다.
package com.jwt.domain.login.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jwt.domain.login.dto.TokenValidationResult;
import com.jwt.domain.login.jwt.token.TokenStatus;
import com.jwt.web.json.ApiResponseJson;
import com.jwt.web.json.ResponseStatusCode;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final String VALIDATION_RESULT_KEY = "result";
private static final String ERROR_MESSAGE_KEY = "errMsg";
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
TokenValidationResult result = (TokenValidationResult) request.getAttribute(VALIDATION_RESULT_KEY);
String errorMessage = result.tokenStatus().getMessage();
int errorCode;
switch (result.tokenStatus()) {
case TOKEN_EXPIRED -> errorCode = ResponseStatusCode.TOKEN_EXPIRED;
case TOKEN_IS_BLACKLIST -> errorCode = ResponseStatusCode.TOKEN_IS_BLACKLIST;
case TOKEN_WRONG_SIGNATURE -> errorCode = ResponseStatusCode.TOKEN_WRONG_SIGNATURE;
case TOKEN_HASH_NOT_SUPPORTED -> errorCode = ResponseStatusCode.TOKEN_HASH_NOT_SUPPORTED;
case WRONG_AUTH_HEADER -> errorCode = ResponseStatusCode.NO_AUTH_HEADER;
default -> {
errorMessage = TokenStatus.TOKEN_VALIDATION_TRY_FAILED.getMessage();
errorCode = ResponseStatusCode.TOKEN_VALIDATION_TRY_FAILED;
}
}
sendError(response, errorMessage, errorCode);
}
private void sendError(HttpServletResponse response, String msg, int code) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiResponseJson responseJson = new ApiResponseJson(HttpStatus.valueOf(HttpServletResponse.SC_UNAUTHORIZED),
code, Map.of(ERROR_MESSAGE_KEY, msg));
String jsonToString = objectMapper.writeValueAsString(responseJson);
response.getWriter().write(jsonToString);
}
}
토큰 검증 결과를 TokenValidationResult DTO에 담아서 오류 메세지를 까보고 그에 맞는 Error를 발생하는 코드입니다.
이로써 Jwt 검증 과정에서 인증
예외 핸들러(JwtAuthenticationEntryPoint
)와 인가
예외 핸들러(JwtAccessDeniedHandler
)를 구현했습니다.
이 핸들러를 Config파일에 등록해줘서 Bean으로 사용할 수 있도록 합니다.
JwtConfig
package com.jwt.domain.config;
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(JwtProperties.class)
public class JwtConfig {
@Bean
public JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint() {
return new JwtAuthenticationEntryPoint();
}
@Bean
public JwtAccessDeniedHandler jwtAccessDeniedHandler() {
return new JwtAccessDeniedHandler();
}
}
요 녀석들 구현 완료