I'm getting this rather weird bug in a test. My controller has the following method:
@Operation(
summary = "Add a new quote",
operationId = "v1AddQuote",
description = "",
responses = [
ApiResponse(responseCode = "201", description = "Created")
]
)
@RequestMapping(
method = [RequestMethod.POST],
value = ["/quote"],
consumes = ["application/json"]
)
@PreAuthorize("hasRole('ADMIN')")
suspend fun v1AddQuote(quoteDto: QuoteDto): ResponseEntity<Unit> {
val entity = quoteRepository.insert(quoteMapper.dtoToEntity(quoteDto))
.awaitSingleOrNull() ?: return ResponseEntity.internalServerError().build()
return ResponseEntity.created(URI.create("/quote/${entity.id}")).build()
}
And I'm testing it with WebTestClient
with:
webClient
.mutateWith(mockUser().roles("ADMIN"))
.post()
.uri("/quote")
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(dto), QuoteDto::class.java)
.exchange()
.expectStatus()
.isCreated
.expectHeader()
.location("/quote/${entity.id}")
Surprisingly, this fails with:
java.lang.IllegalStateException: Could not resolve view with name 'quote'
Where does this even come from? Some details:
@PreAuthorize
solves itLooking at the stack trace it makes me think this is some sort of redirection Spring Security does?
*__checkpoint ⇢ Handler jdk.proxy2.$Proxy126#v1AddQuote(QuoteDto, Continuation) [DispatcherHandler]
*__checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$MutatorFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$SetupMutatorFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP POST "/quote" [ExceptionHandlingWebHandler]
In case it's necessary, my Spring Security configuration is:
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class WebFluxSecurityConfig {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build()
return MapReactiveUserDetailsService(userDetails)
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http.invoke {
authorizeExchange {
authorize(anyExchange, permitAll)
}
csrf { disable() }
httpBasic { }
formLogin { disable() }
logout { disable() }
}
}
}
The problem was actually mostly unrelated to any of the info I gave on the post, and actually solved it almost by pure chance. The issue is that I was inheriting an OpenAPI-generated interface in my controller. I don't know why this gives such a weird error, but the solution is, apparently, to add this to your security @EnableReactiveMethodSecurity
annotation:
@EnableReactiveMethodSecurity(proxyTargetClass = true)
Without the proxyTargetClass = true
parameter, stuff breaks in weird ways.