스프링 테스트에서 보안되지 않은 URL에 대해 401 반환
MVC 테스트에 스프링을 사용하고 있습니다.
여기는 나의 시험 수업이다.
@RunWith(SpringRunner.class)
@WebMvcTest
public class ITIndexController {
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
@MockBean
UserRegistrationApplicationService userRegistrationApplicationService;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
public void should_render_index() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"))
.andExpect(content().string(containsString("Login")));
}
}
MVC 설정을 다음에 나타냅니다.
@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/login/form").setViewName("login");
}
}
다음은 보안 구성입니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/signup/form", "/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login/form").permitAll().loginProcessingUrl("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login/form?logout").permitAll()
.and()
.csrf().disable();
}
@Autowired
public void configureGlobalFromDatabase(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
테스트를 실행하면 실패하고 다음 메시지가 나타납니다.
java.lang.AssertionError: Status expected:<200> but was:<401>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at com.marco.nutri.integration.web.controller.ITIndexController.should_render_index(ITIndexController.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
url이 spring security로 보호되어 있기 때문에 실패하는 것은 이해하지만, 어플리케이션을 실행하면 인증되지 않아도 해당 url에 접속할 수 있습니다.
내가 뭘 잘못하고 있나요?
나는 답을 찾았다.
Spring documents에서는 다음과 같이 말합니다.
@WebMvcTest는 Spring MVC 인프라스트럭처를 자동 설정하고 스캔한 콩을 @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigr 및 HandlerMethodArgumentResolver로 제한합니다.이 주석을 사용할 때 일반 @Component 콩은 검사되지 않습니다.
그리고 github의 이 문제에 따르면:
https://github.com/spring-projects/spring-boot/issues/5476
클래스 경로에 spring-security-test가 있는 경우(이 경우) @WebMvcTest는 기본적으로 스프링보안을 자동으로 설정합니다.
Web Security Configr 클래스가 선택되지 않았기 때문에 기본 보안은 자동으로 설정되어 있습니다.그것이 보안 설정으로 보호되지 않은 url의 401을 수신한 동기입니다.스프링 보안 기본 자동 구성은 기본 인증으로 모든 URL을 보호합니다.
이 문제를 해결하기 위해 제가 한 일은 설명서에서 설명한 것처럼 @ContextConfiguration 및 @MockBean을 사용하여 클래스에 주석을 다는 것입니다.
대부분의 경우 @WebMvcTest는 단일 컨트롤러로 제한되며 @MockBean과 조합하여 사용하여 필요한 공동작업자에게 모의 구현을 제공합니다.
그리고 여기는 시험 수업입니다.
@RunWith(SpringRunner.class)
@WebMvcTest
@ContextConfiguration(classes={Application.class, MvcConfig.class, SecurityConfig.class})
public class ITIndex {
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
@MockBean
UserRegistrationApplicationService userRegistrationApplicationService;
@MockBean
UserDetailsService userDetailsService;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
public void should_render_index() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"))
.andExpect(content().string(containsString("Login")));
}
}
Application, MvcConfig 및 SecurityConfig는 모두 구성 클래스입니다.
원래 질문을 받았을 때 이것이 사용 가능한지는 확실하지 않지만 웹 요청의 보안 부분을 테스트하고 싶지 않은 경우(엔드포인트가 안전하지 않은 것으로 알려진 경우) 단순히 다음을 사용하여 실행할 수 있다고 생각합니다.secure
의 속성@WebMvcTest
주석(기본값)true
그래서 세팅은false
Spring Security의 MockMvc 지원 자동 구성을 비활성화해야 합니다).
@WebMvcTest(secure = false)
자세한 내용은 javadocs를 참조하십시오.
저도 같은 문제를 안고, 여기의 답변과 @Sam Brannen 코멘트의 도움을 받아 문제를 해결했습니다.
@Context Configuration을 사용할 필요는 없습니다.일반적으로 @Import(SecurityConfig.class)를 추가하는 것만으로 충분합니다.
답변을 좀 더 심플화하고 업데이트하기 위해 spring-boot2 프로젝트에서 수정 방법을 공유하겠습니다.
아래 끝점을 테스트하고 싶습니다.
@RestController
@Slf4j
public class SystemOptionController {
private final SystemOptionService systemOptionService;
private final SystemOptionMapper systemOptionMapper;
public SystemOptionController(
SystemOptionService systemOptionService, SystemOptionMapper systemOptionMapper) {
this.systemOptionService = systemOptionService;
this.systemOptionMapper = systemOptionMapper;
}
@PostMapping(value = "/systemoption")
public SystemOptionDto create(@RequestBody SystemOptionRequest systemOptionRequest) {
SystemOption systemOption =
systemOptionService.save(
systemOptionRequest.getOptionKey(), systemOptionRequest.getOptionValue());
SystemOptionDto dto = systemOptionMapper.mapToSystemOptionDto(systemOption);
return dto;
}
}
모든 서비스 메서드는 인터페이스여야 합니다.그렇지 않으면 응용 프로그램콘텍스트를 초기화할 수 없습니다.Security Config를 확인할 수 있습니다.
@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends ResourceServerConfigurerAdapter {
@Autowired
private ResourceServerTokenServices resourceServerTokenServices;
@Override
public void configure(final HttpSecurity http) throws Exception {
if (Application.isDev()) {
http.csrf().disable().authorizeRequests().anyRequest().permitAll();
} else {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().regexMatchers("/health").permitAll()
.antMatchers("/prometheus").permitAll()
.anyRequest().authenticated()
.and()
.authorizeRequests()
.anyRequest()
.permitAll();
http.csrf().disable();
}
}
@Override
public void configure(final ResourceServerSecurityConfigurer resources) {
resources.tokenServices(resourceServerTokenServices);
}
}
아래에는 System Option Controller Test 클래스가 표시됩니다.
@RunWith(SpringRunner.class)
@WebMvcTest(value = SystemOptionController.class)
@Import(SecurityConfig.class)
public class SystemOptionControllerTest {
@Autowired private ObjectMapper mapper;
@MockBean private SystemOptionService systemOptionService;
@MockBean private SystemOptionMapper systemOptionMapper;
@MockBean private ResourceServerTokenServices resourceServerTokenServices;
private static final String OPTION_KEY = "OPTION_KEY";
private static final String OPTION_VALUE = "OPTION_VALUE";
@Autowired private MockMvc mockMvc;
@Test
public void createSystemOptionIfParametersAreValid() throws Exception {
// given
SystemOption systemOption =
SystemOption.builder().optionKey(OPTION_KEY).optionValue(OPTION_VALUE).build();
SystemOptionDto systemOptionDto =
SystemOptionDto.builder().optionKey(OPTION_KEY).optionValue(OPTION_VALUE).build();
SystemOptionRequest systemOptionRequest = new SystemOptionRequest();
systemOptionRequest.setOptionKey(OPTION_KEY);
systemOptionRequest.setOptionValue(OPTION_VALUE);
String json = mapper.writeValueAsString(systemOptionRequest);
// when
when(systemOptionService.save(
systemOptionRequest.getOptionKey(), systemOptionRequest.getOptionValue()))
.thenReturn(systemOption);
when(systemOptionMapper.mapToSystemOptionDto(systemOption)).thenReturn(systemOptionDto);
// then
this.mockMvc
.perform(
post("/systemoption")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(content().string(containsString(OPTION_KEY)))
.andExpect(content().string(containsString(OPTION_VALUE)));
}
}
저는 요.@Import(SecurityConfig.class)
classmvc로 이동합니다.
스프링 부트 2.0+를 사용하고 있는 경우는, 이것을 시험해 주세요.
@WebMvcTest(컨트롤러 = TestController.class, 제외)자동 구성 = {보안}AutoConfiguration.class)
스프링을 사용하는 경우SpringRunner 대신 JUnit4ClassRunner를 통해 보안 계층에서 요청을 수신할 수 있습니다.기본 인증을 사용하는 경우 mockMvc.perform 내의 httpBasic 메서드를 사용해야 합니다.
mockMvc.perform(get("/").with(httpBasic(username,rightPassword))
언급URL : https://stackoverflow.com/questions/39554285/spring-test-returning-401-for-unsecured-urls
'programing' 카테고리의 다른 글
spring-boot에서 swagger-ui를 완전히 비활성화하는 방법(/swagger-ui.html은 404를 반환해야 함 (0) | 2023.03.16 |
---|---|
springboot embedded tomcat 및 tomcat- (0) | 2023.03.16 |
MongoDB와카산드라 (0) | 2023.03.16 |
끌어서 놓기 정렬 가능한 ng:AngularJs에서 반복하시겠습니까? (0) | 2023.03.16 |
Java에서의 JSON 문자열 해석 (0) | 2023.03.11 |