带有post的Spring WebMvcTest返回403



我想知道我的代码的问题在哪里,每次运行后测试(无论它的目标是什么控制器或方法(时,我都会返回403错误,在某些情况下,我会得到401,而在其他情况下,会得到200响应(带auth(。

这是我的控制器的一个片段:

@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
}
}

在我的情况下,csrf保护似乎在我的WebMvcTest中仍然有效(即使在您的配置中禁用(。

因此,为了解决这个问题,我只需将WebMvcTest更改为以下内容:

@Test
public void testFoo() throws Exception {
MvcResult result = mvc.perform(
post("/foo").with(csrf()))
.andExpect(status().isOk())
.andReturn();
// ...
}

因此,丢失的.with(csrf())就是我的问题所在。

您需要在@WebMvcTest(UserController::class)注释之后将@ContextConfiguration(classes=SecurityConfig.class)添加到UserControllerTests类的顶部。

您的问题来自CSRF,如果您启用调试日志记录,问题将变得明显,并且@WebMvcTest只加载web层而不是整个上下文,您的KeycloakWebSecurityConfigurerAdapter没有加载。

加载的配置来自org.springframework.boot.autoconfigure.security.servlet.DefaultConfigurerAdapter(=到org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter

CCD_ 9包含CCD_。

截至今天,您有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

使用弹簧安全csrfSecurityMockMvcRequestPostProcessors

该解决方案体积庞大,容易出错,检查权限拒绝和伪造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(...);
}
}

这里有一个问题:

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。

本教程很好地解释了如何使用mock用户执行测试。

简单的方法是有这样的东西:

@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())
}
}

最新更新