Spring Boot/Test Code

Junit5] JwtAuthenticationFilter 테스트 코드 작성

나른한 찰리 2023. 7. 24. 18:43
반응형

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

https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FilterTests.java

 

위 링크를 참고하여 단위 테스트 작성

전체 코드 중 일부

@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

 

MockMvc no longer handles UTF-8 characters with Spring Boot 2.2.0.RELEASE

After I upgraded to the newly released 2.2.0.RELEASE version of Spring Boot some of my tests failed. It appears that the MediaType.APPLICATION_JSON_UTF8 has been deprecated and is no longer returne...

stackoverflow.com

위 링크를 참고하여, 세 가지 방법을 모두 수행해 보았으나, 여전히 해결되지 않았음

 

시도한 방법 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");


	// ...
    
}

 

 

 

코드 세부 내용

https://github.com/Menjil-Menjil/Menjil-BE/blob/develop/src/test/java/seoultech/capstone/menjil/global/filter/JwtAuthenticationFilterTest.java

 

 

 

 

반응형