带有帖子的 Spring WebMvcTest 返回 403
Posted
技术标签:
【中文标题】带有帖子的 Spring WebMvcTest 返回 403【英文标题】:Spring WebMvcTest with post returns 403 【发布时间】:2019-03-30 08:22:09 【问题描述】:我想知道我的代码的问题出在哪里,每次我运行后测试时(无论它的目标控制器或方法如何),我都会返回 403 错误,而在某些情况下我期望出现 401,并且在其他人中是 200 响应(带有身份验证)。
这是来自我的控制器的 sn-p:
@RestController
@CrossOrigin("*")
@RequestMapping("/user")
class UserController @Autowired constructor(val userRepository: UserRepository)
@PostMapping("/create")
fun addUser(@RequestBody user: User): ResponseEntity<User>
return ResponseEntity.ok(userRepository.save(user))
我的单元测试针对这个控制器
@RunWith(SpringRunner::class)
@WebMvcTest(UserController::class)
class UserControllerTests
@Autowired
val mvc: MockMvc? = null
@MockBean
val repository: UserRepository? = null
val userCollection = mutableListOf<BioRiskUser>()
@Test
fun testAddUserNoAuth()
val user = BioRiskUser(
0L,
"user",
"password",
mutableListOf(Role(
0L,
"administrator"
)))
repository!!
`when`(repository.save(user)).thenReturn(createUser(user))
mvc!!
mvc.perform(post("/create"))
.andExpect(status().isUnauthorized)
private fun createUser(user: BioRiskUser): BioRiskUser?
user.id=userCollection.count().toLong()
userCollection.add(user)
return user
我错过了什么?
根据要求,我的安全配置...
@Configuration
@EnableWebSecurity
class SecurityConfig(private val userRepository: UserRepository, private val userDetailsService: UserDetailsService) : WebSecurityConfigurerAdapter()
@Bean
override fun authenticationManagerBean(): AuthenticationManager
return super.authenticationManagerBean()
override fun configure(auth: AuthenticationManagerBuilder)
auth.authenticationProvider(authProvider())
override fun configure(http: HttpSecurity)
http
.csrf().disable()
.cors()
.and()
.httpBasic()
.realmName("App Realm")
.and()
.authorizeRequests()
.antMatchers("/img/*", "/error", "/favicon.ico", "/doc")
.anonymous()
.anyRequest().authenticated()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/user")
.permitAll()
@Bean
fun authProvider(): DaoAuthenticationProvider
val authProvider = CustomAuthProvider(userRepository)
authProvider.setUserDetailsService(userDetailsService)
authProvider.setPasswordEncoder(encoder())
return authProvider
和身份验证提供者
class CustomAuthProvider constructor(val userRepository: UserRepository) : DaoAuthenticationProvider()
override fun authenticate(authentication: Authentication?): Authentication
authentication!!
val user = userRepository.findByUsername(authentication.name)
if (!user.isPresent)
throw BadCredentialsException("Invalid username or password")
val result = super.authenticate(authentication)
return UsernamePasswordAuthenticationToken(user, result.credentials, result.authorities)
override fun supports(authentication: Class<*>?): Boolean
return authentication?.equals(UsernamePasswordAuthenticationToken::class.java) ?: false
【问题讨论】:
请使用处理身份验证和授权的代码更新您的问题。 你能从邮递员那里调用这个端点吗? 在 Postman 中工作正常(我检查的第一件事) - 我最初没有进行单元测试,但我已经开始使用休息文档,(更多的是让我养成测试所有内容的习惯,而不是懒惰) 【参考方案1】:就我而言,csrf-Protection 在我的 WebMvcTest 中似乎仍然处于活动状态(即使在您的配置中禁用)。
为了解决这个问题,我只是将我的 WebMvcTest 更改为:
@Test
public void testFoo() throws Exception
MvcResult result = mvc.perform(
post("/foo").with(csrf()))
.andExpect(status().isOk())
.andReturn();
// ...
所以我的问题是缺少.with(csrf())
。
【讨论】:
这是唯一适合我的修复方法。否则,当我调试时,我看到它在我的配置中逐步通过 HttpSecurity.csrf().disable(),但后来我看到它在 CrsfFilter 中失败。 csrf()方法来自这个import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;【参考方案2】:您需要在@WebMvcTest(UserController::class)
注释之后将@ContextConfiguration(classes=SecurityConfig.class)
添加到UserControllerTests
类的顶部。
【讨论】:
愚蠢的问题,这不是消除了单元测试的意义吗?我开始把它变成一个集成测试(除非我缺乏理解,我愿意接受)。 使用MockMvc
和mvc.perform()
方法,您实际上并没有编写单元测试。您实际上是在进行 api 调用,因此需要所有配置。如果您要为控制器编写单元测试,您将存根更深的层并直接调用控制器的方法,在您的情况下是 addUser()
方法而不是使用 MockMvc
。【参考方案3】:
您的问题来自 CSRF,如果您启用调试日志记录,问题将变得很明显,并且它来自 @WebMvcTest
仅加载 Web 层而不是整个上下文这一事实,您的 KeycloakWebSecurityConfigurerAdapter
未加载。
加载的配置来自org.springframework.boot.autoconfigure.security.servlet.DefaultConfigurerAdapter
(= 到org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter
包含crsf()
。
截至今天,您有 3 个选项来解决此问题:
选项 1
在您的测试类中创建一个WebSecurityConfigurerAdapter
。
如果您的项目中只有少数 @WebMvcTest
注释类,则该解决方案适合您。
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = MyController.class)
public class MyControllerTest
@TestConfiguration
static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter
@Override
protected void configure(final HttpSecurity http) throws Exception
super.configure(http);
http.csrf().disable();
...
选项 2
在超类中创建一个WebSecurityConfigurerAdapter
并使您的测试从它扩展。
如果您的项目中有多个 @WebMvcTest
注释类,则该解决方案适合您。
@Import(WebMvcTestWithoutCsrf.DefaultConfigWithoutCsrf.class)
public interface WebMvcCsrfDisabler
static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter
@Override
protected void configure(final HttpSecurity http) throws Exception
super.configure(http);
http.csrf().disable();
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = MyControllerTest .class)
public class MyControllerTest implements WebMvcCsrfDisabler
...
选项 3
使用 spring-security csrf SecurityMockMvcRequestPostProcessors
。
这种解决方案体积庞大且容易出错,检查权限拒绝和忘记 with(csrf()) 会导致误报测试。
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = MyController.class)
public class MyControllerTest
...
@Test
public void myTest()
mvc.perform(post("/path")
.with(csrf()) // <=== THIS IS THE PART THAT FIX CSRF ISSUE
.content(...)
)
.andExpect(...);
【讨论】:
您提出的所有选项都有一个很大的缺点:它们针对与生产设置不同的设置执行测试。我希望您提出从***.com/a/52211616/2365727复制的选项 4@【参考方案4】:这里有个问题:
override fun configure(http: HttpSecurity)
http
.csrf().disable()
.cors()
.and()
.httpBasic()
.realmName("App Realm")
.and()
.authorizeRequests()
.antMatchers("/img/*", "/error", "/favicon.ico", "/doc")
.anonymous()
.anyRequest().authenticated()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/user")
.permitAll()
这里更具体:
.anyRequest().authenticated()
您需要对每个请求进行身份验证,因此您会得到 403。
This tutorial 很好地解释了如何使用模拟用户执行测试。
简单的方法是有这样的东西:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SecuredControllerRestTemplateIntegrationTest
@Autowired
private val template: TestRestTemplate
@Test
fun createUser(): Unit
val result = template.withBasicAuth("username", "password")
.postForObject("/user/create", HttpEntity(User(...)), User.class)
assertEquals(HttpStatus.OK, result.getStatusCode())
【讨论】:
但是我不想要一个“完整”的 servlet 容器,如果我正确使用测试(?)或者 Spring Boot WebMvcTest 有缺陷,我不应该需要一个? (虽然我也对“新锤子”问题持开放态度)以上是关于带有帖子的 Spring WebMvcTest 返回 403的主要内容,如果未能解决你的问题,请参考以下文章
@MockBean 不适用于带有 JUnit 5 和 Spring Boot 2 的 @WebMvcTest?
在 Spring Boot 中禁用 @WebMvcTest 的 Spring Security 配置类
spring boot test中如何配置HandlerMethodArgumentResolver
Spring 的 @WebMvcTest 不适用于 Java 8 类型
在 Spring Boot 测试类上使用 @WebMvcTest 注释时出错
错误:在为 Spring Controller 执行 @WebMvcTest 时找不到 @SpringBootConfiguration