为啥我收到 MockMvc 和 JUnit 的错误 403?
Posted
技术标签:
【中文标题】为啥我收到 MockMvc 和 JUnit 的错误 403?【英文标题】:Why I received an Error 403 with MockMvc and JUnit?为什么我收到 MockMvc 和 JUnit 的错误 403? 【发布时间】:2014-03-12 01:38:11 【问题描述】:我有一个带有 spring security (3.2) 的 spring mvc (3.2.5) 应用程序。
我用这个方法配置了我的 SecurityConfig.class :
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests().antMatchers("/*").permitAll().and()
.formLogin().successHandler(successHandler)
.defaultSuccessUrl("/")
.failureHandler(failureHandler).failureUrl("/login?error=true")
.permitAll().and().logout()
.permitAll();
http.authorizeRequests().antMatchers("/resources/**").permitAll();
http.authorizeRequests().antMatchers("/welcome").permitAll();
http.authorizeRequests().antMatchers("/secure/*").authenticated();
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated();
使用 Spring security (3.2) 我启用了 CSRF。我认为启用它是个好主意。
我的控制器 SignInController 包含 2 个带参数的方法:
编辑:在参数中添加action=
@RequestMapping(value = "/signup")
public ModelAndView signup()
boolean auth = SecurityContextHolder.getContext().getAuthentication() == null ? false
: SecurityContextHolder.getContext().getAuthentication()
.isAuthenticated()
&& (SecurityContextHolder.getContext()
.getAuthentication().getPrincipal() instanceof User);
ModelAndView result = null;
if (auth)
result = new ModelAndView("redirect:" + "/");
else
UserForm user = new UserForm();
result = new ModelAndView("registration", "userForm", user);
return result;
@RequestMapping(value = "/register", params = "action=signup")
public ModelAndView registration(
@ModelAttribute(value = "userForm") @Valid UserForm userForm,
BindingResult result, HttpServletRequest request)
if (result.hasErrors())
return new ModelAndView("registration");
Member member = profileFacade.registerNewUser(userForm);
return new ModelAndView("registration", "member", member);
@RequestMapping(value = "/register", params = "action=cancel")
public ModelAndView cancelRegistration()
return new ModelAndView("redirect:" + "/");
最后,我进行了 JUnit 测试:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfiguration.class,
JpaConfiguration.class, LoggingConfiguration.class,
SecurityConfig.class, DataSourceEmbeddedConfiguration.class,
DataSourcemysqlConfig.class, BaseValidatorConfiguration.class )
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@ActiveProfiles("dev")
public class SignInControllerTest
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private MockHttpSession session;
@Autowired
private MockHttpServletRequest request;
@Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mockMvc;
@Before
public void setUp() throws ServletException
SecurityContextHolderAwareRequestFilter scharf = new SecurityContextHolderAwareRequestFilter();
scharf.afterPropertiesSet();
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.webApplicationContext)
.addFilters(springSecurityFilterChain).dispatchOptions(true).build();
SecurityContextHolder.getContext().setAuthentication(null);
@Test
public void signup() throws Exception
mockMvc.perform(get("/signup")).andExpect(status().isOk())
.andExpect(model().attributeExists("userForm"));
@Test
@Transactional
@Rollback(true)
public void register() throws Exception
UserForm form = new UserForm();
form.setEmail("email@email.com");
form.setUsername("aokije");
form.setPassword("klo,ksff");
form.setConfirmedPassword("klo,ksff");
mockMvc.perform(post("/register").param("action", "signup")).andExpect(status().isOk());
编辑:更新 mockMvc.perform,因为它与 SecurityConfig.class 中的 http.csrf().disable()
一起工作正常
测试 signup 运行良好,但 register 返回错误 403。 我尝试了很多东西,但总是收到这个错误。
当我在浏览器中尝试http://localhost:8080/register?signup
时,它工作正常。
_编辑_
日志:
2014-02-13 22:00:14,695 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@52ee705c
2014-02-13 22:00:14,696 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@2412d28d
2014-02-13 22:00:14,697 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@4fbd397b
2014-02-13 22:00:14,697 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/logout']
2014-02-13 22:00:14,698 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@1008e323
2014-02-13 22:00:14,699 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/*']
2014-02-13 22:00:14,700 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/resources/**']
2014-02-13 22:00:14,700 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/welcome']
2014-02-13 22:00:14,700 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'authenticated', for Ant [pattern='/secure/*']
2014-02-13 22:00:14,701 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'hasRole('ROLE_ADMIN')', for Ant [pattern='/admin/**']
2014-02-13 22:00:14,701 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'authenticated', for org.springframework.security.web.util.matcher.AnyRequestMatcher@1
2014-02-13 22:00:14,703 [FilterSecurityInterceptor] afterPropertiesSet Validated configuration attributes
2014-02-13 22:00:14,704 [FilterSecurityInterceptor] afterPropertiesSet Validated configuration attributes
2014-02-13 22:00:14,734 [DefaultSecurityFilterChain] <init> Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@10174779, org.springframework.security.web.context.SecurityContextPersistenceFilter@68736a7e, org.springframework.security.web.header.HeaderWriterFilter@728e5d0d, org.springframework.security.web.csrf.CsrfFilter@6e7a918b, org.springframework.security.web.authentication.logout.LogoutFilter@430e85e7, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@55eda087, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@290c7ca, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6dd90afc, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@12eb6a0f, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6855612f, org.springframework.security.web.session.SessionManagementFilter@410a11a2, org.springframework.security.web.access.ExceptionTranslationFilter@59e15580, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2257a0]
2014-02-13 22:00:14,859 [FilterChainProxy] doFilter /register at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2014-02-13 22:00:14,863 [FilterChainProxy] doFilter /register at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2014-02-13 22:00:14,863 [HttpSessionSecurityContextRepository] readSecurityContextFromSession HttpSession returned null object for SPRING_SECURITY_CONTEXT
2014-02-13 22:00:14,863 [HttpSessionSecurityContextRepository] loadContext No SecurityContext was available from the HttpSession: org.springframework.mock.web.MockHttpSession@4c4b529f. A new one will be created.
2014-02-13 22:00:14,864 [FilterChainProxy] doFilter /register at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2014-02-13 22:00:14,865 [HstsHeaderWriter] writeHeaders Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@5ab39e58
2014-02-13 22:00:14,865 [FilterChainProxy] doFilter /register at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2014-02-13 22:00:14,866 [CsrfFilter] doFilterInternal Invalid CSRF token found for http://localhost/register
2014-02-13 22:00:14,866 [HttpSessionSecurityContextRepository] saveContext SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2014-02-13 22:00:14,866 [SecurityContextPersistenceFilter] doFilter SecurityContextHolder now cleared, as request processing completed
你能帮我吗?
非常感谢
编辑
最后,我在另一个类(注释)中遇到了一个错误。我解决了这个问题:
HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository = new HttpSessionCsrfTokenRepository();
CsrfToken csrfToken = httpSessionCsrfTokenRepository
.generateToken(request);
Map map = new HashMap();
map.put("userForm", form);
map.put("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN",
csrfToken);
this.mockMvc
.perform(
post("/register")
.param("signup", "")
.param("_csrf", csrfToken.getToken())
.sessionAttrs(map)).andExpect(status().isOk());
参数 csrf 和 sessionAttrs 是强制性的。
【问题讨论】:
【参考方案1】:我知道这个问题已经很老了,但这是谷歌上一些查询的第一批结果之一,我相信这种方法要好得多,它是described on spring.io blog
1) 您可以使用 Spring Security 支持更轻松地创建您的 mockMvc
,因此您的 setUp()
会变得更短:
@Before
public void setUp() throws Exception
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
2) 您可以使用org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf()
使用正确的 CSRF 令牌填充您的测试请求,如下所示:
mockMvc.perform(post("/register")
.with(csrf())
.param("action", "signup"))
.andExpect(status().isOk());
【讨论】:
太棒了,刚刚救了我。当您在安全配置中有http.csrf().disable()
时,它是必需的。
@AnilBhaskar 为什么指定 csrf 被禁用时必须使用它?
如果您正在 spring-boot2 中寻找答案,您可以查看我的答案,该答案解决了有关 http 403 和 http 401 问题的两个问题。 ***.com/a/54839518/1577363
这个答案对我有用,比接受的答案简单得多。
漂亮而简单。您还可以使用 @Autowired MockMvc mvc
设置 MockMvc 并在您的测试方法中使用 @WithMockUser。【参考方案2】:
发布请求需要将 CSRF 令牌添加到表单中。所以你必须在测试时通过它:
var TOKEN_ATTR_NAME = "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN";
var httpSessionCsrfTokenRepository = new HttpSessionCsrfTokenRepository();
var csrfToken = httpSessionCsrfTokenRepository.generateToken(new MockHttpServletRequest());
mockMvc.perform(
post("/your/path/here")
.sessionAttr(TOKEN_ATTR_NAME, csrfToken)
.param(csrfToken.getParameterName(), csrfToken.getToken())
...
);
第二件事,你确定注册方法能处理你的发帖请求吗? RequestMapping 不是默认为“GET”配置的吗?
【讨论】:
谢谢,但它不起作用。我试过这个:它不工作。我试过这个:mockMvc.perform(post("/register").param("action", "signup").session(session).sessionAttr("_csrf", csrfToken)).andExpect( status().isOk());
我收到了同样的错误。如果我在我的 SecurityConfig 中使用 http.csrf().disable() 禁用 csrf,它工作正常(没有 .sessionAttr())
尝试使用“org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN”;取而代之的是“_csrf”,忘记发布了; )
我无权访问常量 CSRF_TOKEN。它在课堂上不存在。所有其他常量都是私有的。
它唯一的字符串,会话中属性的键。只需将其作为第一个参数传递: .sessionAttr("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN", csrfToken)
问题依然存在。我用 HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN") 进行了测试,但它也失败了。如果我检查 csrfToken,paramvalue 是“_csrf”。【参考方案3】:
试试@AutoConfigureMockMvc(addFilters = false)
【讨论】:
以上是关于为啥我收到 MockMvc 和 JUnit 的错误 403?的主要内容,如果未能解决你的问题,请参考以下文章
使用 spring-data-jpa 和 MockMvc 进行 spring boot junit 测试
如何正确使用 MockMvc 在 Junit 中测试 post 方法?
在 Spring App 中将 MockitoMVC 与 Junit 一起使用时出现 *** 错误