spring security test code 작성시 UserDetails가 필요할때
스프링 시큐리티 테스트 코드 작성시 ‘@WithMockUser’ 어너테이션을 사용해서 인증을 통과 시킨다
하지만 어떤 API는 UserDetails를 필요로 할수 있다 그럴때 커스텀 어너테이션을 만들어서 사용할수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Configuration public class SecurityConfigruation extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("**").authenticated().and().formLogin() .disable().csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and() .exceptionHandling() .authenticationEntryPoint(new Http403ForbiddenEntryPoint()) .accessDeniedHandler(new AccessDeniedHandlerImpl()); }
}
|
위처럼 간단하게 인증을 걸고 API를 하나 만들어서 테스트 코드를 작성해서 테스트 해보았다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Slf4j @RequestMapping("/api/") @RestController public class TestController {
@GetMapping("test") public ResponseEntity<String> getTest(Authentication principal) {
var userName = principal.getName(); log.debug("test ok {}", principal.getDetails());
return ResponseEntity.ok(userName); } }
|
위에 컨트롤러 하나 생성후에 테스트 코드를 작성해 보면
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc class TestControllerTest {
private static final String URL = "/api/test";
@Autowired private MockMvc mockMvc;
@DisplayName("인증실패") @Test void forbiddenTest() throws Exception { this.mockMvc.perform(get(URL) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(print()).andExpect(status().isForbidden()); }
@DisplayName("WithMockUser 어너테이션 테스트") @WithMockUser @Test void withMockTest() throws Exception { this.mockMvc.perform(get(URL) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(print()).andExpect(status().isOk()); }
}
|
인증실패 결과
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| MockHttpServletRequest: HTTP Method = GET Request URI = /api/test Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json"] Body = null Session Attrs = {}
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 = 403 Error message = Access Denied Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", 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 = []
|
WithMockUser 어너테이션 테스트 결과
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| MockHttpServletRequest: HTTP Method = GET Request URI = /api/test Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json"] Body = null Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@ca25360: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER}
Handler: Type = io.github.sejoung.controller.TestController Method = io.github.sejoung.controller.TestController#getTest(Authentication)
Async: Async started = false Async result = null
Resolved Exception: Type = null
ModelAndView: View name = null View = null Model = null
FlashMap: Attributes = null
MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"4", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = application/json Body = user Forwarded URL = null Redirected URL = null Cookies = []
|
위에 결과를 보면 인증은 통과 했지만 Details: null;
이다.
그럼 값을 넣어 주는것을 테스트 해서 보면
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.test.context.support.WithSecurityContext;
@Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) public @interface WithMockCustomUser {
String username() default "sejoung";
String name() default "sejoung kim"; }
|
위 처럼 어너테이션을 생성 하고
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
@Override public SecurityContext createSecurityContext(WithMockCustomUser customUser) { SecurityContext context = SecurityContextHolder.createEmptyContext(); List<GrantedAuthority> grantedAuthorities = new ArrayList(); grantedAuthorities.add(new SimpleGrantedAuthority("USER")); User principal = new User(customUser.username(), "1234", true, true, true, true, grantedAuthorities); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( principal, principal.getPassword(), principal.getAuthorities());
authentication.setDetails(new Detail(customUser.username(), "aaaa")); context.setAuthentication(authentication); return context; } }
|
위 처럼 팩토리를 하나 만들어서 사용하면
테스트 코드는 하나 추가해서 보면
1 2 3 4 5 6 7 8 9
| @DisplayName("커스텀 어너테이션 테스트") @WithMockCustomUser @Test void customWithMockTest() throws Exception { this.mockMvc.perform(get(URL) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(print()).andExpect(status().isOk()); }
|
커스텀 어너테이션 테스트 결과
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| MockHttpServletRequest: HTTP Method = GET Request URI = /api/test Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json"] Body = null Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@b3ebb9fe: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@b3ebb9fe: Principal: org.springframework.security.core.userdetails.User@75d00a77: Username: sejoung; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: USER; Credentials: [PROTECTED]; Authenticated: true; Details: Detail(name=sejoung, data=aaaa); Granted Authorities: USER}
Handler: Type = io.github.sejoung.controller.TestController Method = io.github.sejoung.controller.TestController#getTest(Authentication)
Async: Async started = false Async result = null
Resolved Exception: Type = null
ModelAndView: View name = null View = null Model = null
FlashMap: Attributes = null
MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"7", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"] Content type = application/json Body = sejoung Forwarded URL = null Redirected URL = null Cookies = []
|
Details: Detail(name=sejoung, data=aaaa)
이렇게 값이 들어간것을 볼수 있다.
참조