Spring

[Spring] Spring Security 테스트 코드 401 에러 | Spring Security 테스트 코드 401 에러 해결법

kimslab01 2024. 10. 6. 03:09

 

 

 

 

 

 

스프링 시큐리티를 적용하고 나서 TodoController를 이렇게 작성해주었다.

@WebMvcTest(TodoController.class)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;

    @MockBean
    private JwtUtil jwtUtil;

    @Test
    void todo_단건_조회에_성공한다() throws Exception {
        // given
        long todoId = 1L;
        String title = "title";
        CustomUserDetails authUser = new CustomUserDetails(1L, "email", "test1234!", UserRole.USER, "nickname");
        User user = User.fromAuthUser(authUser);
        UserResponse userResponse = new UserResponse(user.getId(), user.getEmail());
        TodoResponse response = new TodoResponse(
                todoId,
                title,
                "contents",
                "Sunny",
                userResponse,
                LocalDateTime.now(),
                LocalDateTime.now()
        );

        // 실제 JWT 토큰 생성
        String token = jwtUtil.createToken(authUser.getId(), authUser.getEmail(), authUser.getUserRole(), authUser.getNickname());

        // when
        when(todoService.getTodo(todoId)).thenReturn(response);

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(todoId))
                .andExpect(jsonPath("$.title").value(title));
    }

    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        // given
        long todoId = 1L;

        CustomUserDetails authUser = new CustomUserDetails(1L, "test@email.com", "test1234!", UserRole.USER, "test");
        String token = jwtUtil.createToken(authUser.getId(), authUser.getEmail(), authUser.getUserRole(), authUser.getNickname());

        // when
        when(todoService.getTodo(todoId))
                .thenThrow(new InvalidRequestException("Todo not found"));

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId)
                        .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.status").value(HttpStatus.BAD_REQUEST.name()))
                .andExpect(jsonPath("$.code").value(HttpStatus.BAD_REQUEST.value()))
                .andExpect(jsonPath("$.message").value("Todo not found"));
    }
}

 

 

실제 토큰을 생성해서 인증하는 방식을 사용하려고 했는데,

더보기

> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
02:50:00.053 [Test worker] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.example.expert.domain.todo.controller.TodoControllerTest]: TodoControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
02:50:00.293 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.example.expert.ExpertApplication for test class org.example.expert.domain.todo.controller.TodoControllerTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.3)

2024-10-06T02:50:00.977+09:00  INFO 18892 --- [    Test worker] o.e.e.d.t.controller.TodoControllerTest  : Starting TodoControllerTest using Java 17.0.12 with PID 18892 (started by kikye in C:\Users\kikye\Desktop\Spring\spring-plus)
2024-10-06T02:50:00.979+09:00  INFO 18892 --- [    Test worker] o.e.e.d.t.controller.TodoControllerTest  : No active profile set, falling back to 1 default profile: "default"
2024-10-06T02:50:03.952+09:00  WARN 18892 --- [    Test worker] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: cb0bc4f3-7e7f-4255-a447-285cdfb43777

This generated password is for development use only. Your security configuration must be updated before running your application in production.

2024-10-06T02:50:04.523+09:00  WARN 18892 --- [    Test worker] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates, check your Thymeleaf configuration, or set spring.thymeleaf.check-template-location=false)
2024-10-06T02:50:04.596+09:00  INFO 18892 --- [    Test worker] r$InitializeUserDetailsManagerConfigurer : Global AuthenticationManager configured with UserDetailsService bean with name inMemoryUserDetailsManager
2024-10-06T02:50:04.788+09:00  INFO 18892 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2024-10-06T02:50:04.788+09:00  INFO 18892 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2024-10-06T02:50:04.790+09:00  INFO 18892 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 2 ms
2024-10-06T02:50:04.830+09:00  INFO 18892 --- [    Test worker] o.e.e.d.t.controller.TodoControllerTest  : Started TodoControllerTest in 4.385 seconds (process running for 6.255)

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /todos/1
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Authorization:"Bearer null"]
             Body = null
    Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost/todos/1?continue]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = Unauthorized
          Headers = [WWW-Authenticate:"Basic realm="Realm"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Status
Expected :200
Actual   :401
<Click to see difference>

java.lang.AssertionError: Status expected:<200> but was:<401>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:637)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:214)
at org.example.expert.domain.todo.controller.TodoControllerTest.todo_단건_조회에_성공한다(TodoControllerTest.java:68)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)


MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /todos/1
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Authorization:"Bearer null"]
             Body = null
    Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost/todos/1?continue]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = Unauthorized
          Headers = [WWW-Authenticate:"Basic realm="Realm"", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Status
Expected :400
Actual   :401
<Click to see difference>

java.lang.AssertionError: Status expected:<400> but was:<401>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:637)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:214)
at org.example.expert.domain.todo.controller.TodoControllerTest.todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다(TodoControllerTest.java:89)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)



> Task :test
TodoControllerTest > todo_단건_조회에_성공한다() FAILED
    java.lang.AssertionError at TodoControllerTest.java:68
TodoControllerTest > todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() FAILED
    java.lang.AssertionError at TodoControllerTest.java:89
2 tests completed, 2 failed
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///C:/Users/kikye/Desktop/Spring/spring-plus/build/reports/tests/test/index.html
* Try:
> Run with --scan to get full insights.
BUILD FAILED in 8s
4 actionable tasks: 1 executed, 3 up-to-date

이러한 에러가 발생했다.

주요 내용을 보니 Authorization:"Bearer null"이 되어 토큰이 발생하지 않는다는 것이 원인이었다는 것을 알았다.

 

https://devjh.tistory.com/324

 

[Junit] 유닛 테스트에서 응답코드가 401로 뜨는 경우 해결방법

크롬을 켜서 실제 테스트로 진행하면 200 http response code가 뜨지만 유닛 테스트를 돌려서 테스트를 진행하면 401 오류가 발생하는 경우가 있다. java.lang.AssertionError: Status expected: but was: 필요:200 실제

devjh.tistory.com

https://velog.io/@tjdtn0219/SpringSecurity%EC%A0%81%EC%9A%A9-%ED%9B%84-Controller-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EC%8B%9C-%EB%B0%9C%EC%83%9D%ED%96%88%EB%8D%98-%EC%98%A4%EB%A5%98%EB%93%A4-Feat.-Junit5-csrf

 

[Spring]Security적용 후 Controller 테스트코드 작성 시 발생했던 오류들 (Feat. Junit5, csrf)

Controller에서 모든 Board를 조회하는 메소드를 테스트 코드를 작성하던 중 일어난 일이다. 우선 코드를 먼저보자 사실 이 로직상은 아무 문제가 없다.하지만 다음과 같이 401 Unauthorized가 발생하는

velog.io

 

두 블로그를 정독하여 읽어보니,

내가 쓴 테스트 코드에서는 JwtFilter 부분으로 권한이 넘어가지 않아서 그렇다는 것을 알았다.

 

 

 

 

 

그래서 @WithMockUser(rolse = "USER")를 사용해서

USER라는 권한을 추가해서 테스트를 해보았다.

그러니 정상적으로 확인하는 것을 알 수 있었다.