java Spring 测试为不安全的 URL 返回 401
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39554285/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Test returning 401 for unsecured URLs
提问by Marco Prado
I am using Spring for MVC tests
我正在使用 Spring 进行 MVC 测试
Here is my test class
这是我的测试课
@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")));
}
}
Here is the MVC config
这是MVC配置
@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/login/form").setViewName("login");
}
}
Here is the security config
这是安全配置
@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);
}
}
When I run my test it fails with the message:
当我运行测试时,它失败并显示以下消息:
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.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc.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.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.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access@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")));
}
}
0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.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)
I understand that it fails due to the fact that the url is protected with spring security, but when I run my application I can access that url even without being authenticated.
我知道它失败是因为 url 受 spring 安全保护,但是当我运行我的应用程序时,即使没有经过身份验证,我也可以访问该 url。
Am I doing something wrong?
难道我做错了什么?
回答by Marco Prado
I found the answer
Spring docs says that:
我找到了答案
Spring docs 说:
@WebMvcTest will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular @Component beans will not be scanned when using this annotation.
@WebMvcTest 将自动配置 Spring MVC 基础结构并将扫描的 bean 限制为 @Controller、@ControllerAdvice、@JsonComponent、Filter、WebMvcConfigurer 和 HandlerMethodArgumentResolver。使用此注释时不会扫描常规 @Component bean。
And according to this issue in github:
根据github中的这个问题:
https://github.com/spring-projects/spring-boot/issues/5476
https://github.com/spring-projects/spring-boot/issues/5476
The @WebMvcTest by default auto configure spring security if spring-security-test is present in the class path (which in my case is).
如果 spring-security-test 存在于类路径中(在我的例子中是),@WebMvcTest 默认会自动配置 spring 安全性。
So since WebSecurityConfigurer classes aren't picked, the default security was being auto configured, that is the motive I was receiving the 401 in url's that was not secured in my security configuration. Spring security default auto configuration protects all url's with basic authentication.
因此,由于未选择 WebSecurityConfigurer 类,因此自动配置了默认安全性,这就是我收到 url 中未在我的安全配置中保护的 401 的动机。 Spring 安全默认自动配置使用基本身份验证保护所有 url。
What I did to solve the problem was to annotate the class with @ContextConfiguration, and @MockBean like it is described in the documentation:
我为解决这个问题所做的是用@ContextConfiguration 和 @MockBean 注释类,就像文档中描述的那样:
Often @WebMvcTest will be limited to a single controller and used in combination with @MockBean to provide mock implementations for required collaborators.
通常@WebMvcTest 将被限制为单个控制器并与@MockBean 结合使用为所需的协作者提供模拟实现。
And here is the test class
这是测试类
@WebMvcTest(secure = false)
Application, MvcConfig and SecurityConfig are all my configuration classes
Application、MvcConfig 和 SecurityConfig 都是我的配置类
回答by Michael Damone
Not sure if this was available when the original question was asked, but if truly not wanting to test the security portion of a web request (which seems reasonable if the endpoint is known to be unsecure), then I think this could be done simply by using the secure
attribute of the @WebMvcTest
annotation (it defaults to true
so setting it to false
should disable the auto-configuration of Spring Security's MockMvc support):
不确定在提出原始问题时这是否可用,但如果真的不想测试 Web 请求的安全部分(如果已知端点不安全,这似乎是合理的),那么我认为这可以简单地通过使用注解的secure
属性@WebMvcTest
(它默认为true
所以设置它false
应该禁用 Spring Security 的 MockMvc 支持的自动配置):
@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;
}
}
More info available in the javadocs
javadocs 中提供了更多信息
回答by erhun
I had an some problem and solve the issue with the help of the answers here and @Sam Brannen comment.
我遇到了一些问题,并在此处的答案和@Sam Brannen 评论的帮助下解决了该问题。
You probably don't need to use @ContextConfiguration. Simply adding @Import(SecurityConfig.class) should typically suffice.
您可能不需要使用@ContextConfiguration。简单地添加 @Import(SecurityConfig.class) 通常就足够了。
To simplify and update the answers a bit more I want to share how i fix it in my spring-boot2 project.
为了更简化和更新答案,我想分享我如何在我的 spring-boot2 项目中修复它。
I want to test below endpoint.
我想测试以下端点。
@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);
}
}
All service methods must be interface otherwise application context can't be initialized. You can check my SecurityConfig.
所有服务方法都必须是接口,否则无法初始化应用程序上下文。您可以查看我的 SecurityConfig。
@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)));
}
}
And below you can see my SystemOptionControllerTestclass.
在下面你可以看到我的SystemOptionControllerTest类。
mockMvc.perform(get("/").with(httpBasic(username,rightPassword))
So I just need to add @Import(SecurityConfig.class)
to my mvc test class.
所以我只需要添加@Import(SecurityConfig.class)
到我的 mvc 测试类。
回答by mertaksu
If you use SpringJUnit4ClassRunner instead of SpringRunner you can catch your requests in security layer. If you are using basic authentication you have to user httpBasic method inside mockMvc.perform
如果您使用 SpringJUnit4ClassRunner 而不是 SpringRunner,您可以在安全层中捕获您的请求。如果您使用基本身份验证,则必须在 mockMvc.perform 中使用 httpBasic 方法
##代码##