반응형
Spring Boot에서, Servlet Filter에 대한 테스트 코드 작성을 한 내용은 많지 않아서, 간단히 내 코드를 기록.
JwtAuthenticationFilter
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final ObjectMapper objectMapper;
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private final String HEADER_DATA_VALUE = "None";
private final String ACCESS_TOKEN_IS_NOT_VALID = "Access_Token_is_not_valid";
private final String REFRESH_TOKEN_IS_NOT_VALID = "Refresh_Token_is_not_valid";
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, ObjectMapper objectMapper) {
this.jwtTokenProvider = jwtTokenProvider;
this.objectMapper = objectMapper;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
log.info(">> Enter the JwtAuthenticationFilter");
String[] jwtTokenArr = resolveAuthorizationBearer(request);
String accessToken;
String refreshToken;
/* case 1: Header 에 Authorization 값이 존재하지 않거나, Authorization 이 Bearer 타입이 아닌 경우 */
if (jwtTokenArr == null) {
ConcurrentHashMap<String, Object> detailsMap = responseJson(403,
"Header의 Authorization 값이 존재하지 않거나, " +
"혹은 Authorization에서 Bearer 타입이 존재하지 않습니다", HEADER_DATA_VALUE);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), detailsMap);
return;
}
/* case 2: Authorization 에서 Bearer 값이 2개(Access Token, Refresh Token)이 아닌 경우,
혹은 공백으로 분리되어 있지 않은 경우 */
else if (jwtTokenArr.length > 2) {
ConcurrentHashMap<String, Object> detailsMap = responseJson(403,
"Authorization의 Bearer 값이 잘못되었습니다", HEADER_DATA_VALUE);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), detailsMap);
return;
}
/* case 3: Access Token 과 함께 요청이 들어오는 경우 */
else if (jwtTokenArr.length == 1) {
accessToken = jwtTokenArr[0];
TokenStatus accessTokenIsAvailable = jwtTokenProvider.validateAccessToken(accessToken);
if (accessTokenIsAvailable == TokenStatus.RELIABLE) {
/* 정상 요청 */
filterChain.doFilter(request, response);
} else {
/* Refresh Token을 담아서 보내도록 클라이언트에게 요청 */
ConcurrentHashMap<String, Object> detailsMap = responseJson(403,
"Access Token 값이 유효하지 않습니다", ACCESS_TOKEN_IS_NOT_VALID);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), detailsMap);
return;
}
}
/* case 4: Access Token, Refresh Token 이 모두 담겨서 요청이 들어오는 경우 */
else if (jwtTokenArr.length == 2) {
accessToken = jwtTokenArr[0];
refreshToken = jwtTokenArr[1];
TokenStatus refreshTokenIsAvailable = jwtTokenProvider.validateRefreshToken(refreshToken);
if (refreshTokenIsAvailable == TokenStatus.RELIABLE) {
/* Access Token 재발급 */
String userId = jwtTokenProvider.getUserId(refreshToken);
String newAccessToken = jwtTokenProvider.generateAccessToken(userId, LocalDateTime.now());
Map<String, String> map = new ConcurrentHashMap<>();
map.put("accessToken", newAccessToken);
ConcurrentHashMap<String, Object> detailsMap = responseJson(201,
"Access Token이 재발급 되었습니다", map);
response.setStatus(HttpStatus.CREATED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), detailsMap);
return;
} else {
/* 재로그인 요청 */
ConcurrentHashMap<String, Object> detailsMap = responseJson(403,
"Refresh Token 값이 유효하지 않습니다. 재로그인 해주세요", REFRESH_TOKEN_IS_NOT_VALID);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), detailsMap);
return;
}
}
}
private String[] resolveAuthorizationBearer(HttpServletRequest request) {
String headerAuth = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith(BEARER_PREFIX)) {
// Access Token, Refresh Token 모두 담는다.
return headerAuth.substring(7).split(" ");
}
return null;
}
public ConcurrentHashMap<String, Object> responseJson(int code, String message, Object data) {
ConcurrentHashMap<String, Object> errorDetails = new ConcurrentHashMap<>();
errorDetails.put("code", code); // 403: forbidden
errorDetails.put("message", message);
errorDetails.put("data", data);
return errorDetails;
}
}
JwtAuthenticationFilterTest
위 링크를 참고하여 단위 테스트 작성
전체 코드 중 일부
@Test
@DisplayName("doFilter로 요청이 들어올 때, header 에 Authentication 정보가 없는 경우")
void doFilterInAuthenticationIsNone() throws Exception {
standaloneSetup(new TokenTestController())
.addFilters(new JwtAuthenticationFilter(
new JwtTokenProvider(tokenKey, userRepository, tokenRepository), objectMapper))
.build()
.perform(post("/api/user/hello")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden())
.andExpect(MockMvcResultMatchers.content().encoding("UTF-8")) // Verify the encoding
.andExpect(jsonPath("$.code", is(403)))
.andExpect(jsonPath("$.message",
is("Header의 Authorization 값이 존재하지 않거나, 혹은 Authorization에서 Bearer 타입이 존재하지 않습니다")))
.andExpect(jsonPath("$.data", is("None")))
.andDo(print());
}
테스트 코드 실행 결과
java.lang.AssertionError: Character encoding expected:<UTF-8> but was:<ISO-8859-1>
MockMvc no longer handles UTF-8 characters with Spring Boot 2.2.0.RELEASE
위 링크를 참고하여, 세 가지 방법을 모두 수행해 보았으나, 여전히 해결되지 않았음
시도한 방법 1
- 결과: 해결되지 않음
@Configuration
public class WebConfig implements WebMvcConfigurer {
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(converter -> ((MappingJackson2HttpMessageConverter) converter)
.setDefaultCharset(StandardCharsets.UTF_8));
}
}
시도한 방법 2
- 결과: 해결되지 않음
@BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(((request, response, chain) -> {
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
})).build();
}
해결 방법
JwtAuthenticationFilter에, 아래 코드 한 줄 추가
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
// ...
}
코드 세부 내용
반응형
'Spring Boot > Test Code' 카테고리의 다른 글
Junit5] MockMvc equals() and hashcode() (0) | 2023.07.23 |
---|---|
Junit5] MongoDB 테스트 환경 세팅 (0) | 2023.07.17 |
Junit5] WebMvcTest: java.lang.AssertionError: JSON path (0) | 2023.07.15 |
Junit5] 테스트 코드 작성을 위한 서비스 코드 리팩토링 (0) | 2023.07.14 |
Junit5] org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false (0) | 2023.07.08 |