spring-projects / spring-projects/spring-security
hasAuthority and custom Mono<Boolean> method in @PreAuthorize leads to ConverterNotFoundException error
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.6k
- Forks
- 6.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 52
Description
Describe the bug
After upgrading to Spring Boot 3.3.0 and Spring Security 6.3.0 I've tried to migrate my single Mono<Boolean> @PreAuthorize calls to more complex ones as I thought that these tasks make it possible:
- https://github.com/spring-projects/spring-security/issues/9401
- https://github.com/spring-projects/spring-security/issues/4841
However, if we want to use the @PreAuthorize connected with a Mono<Boolean> and hasAuthority we are getting an error No converter found capable of converting from type [reactor.core.publisher.MonoFlatMap<java.lang.Boolean, ?>] to type [java.lang.Boolean]
To Reproduce
- Create a WebFlux controller and use
@EnableReactiveMethodSecurityconfiguration - Define Controller with a
@PreAuthorizeusage - Add verification returning
Mono<Boolean>and call the endpoint - it works - Connect the custom method authorization with built-in
@PreAuthorize- the exception will be thrown on call
Expected behaviour
The "complex" @PreAuthorize expression should be handled without isues if we use methods returning Mono<Boolean> and built-in hasAuthority calls.
Sample
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
}
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/test")
public class TestController {
@GetMapping("/values")
@PreAuthorize("hasAuthority('ppcv:browse') || "
+ "(hasAuthority('ppcv:browse-scoped') && @userConnectionAuthorizerService.isUserConnected(#id))")
public Mono<ApiResponse<List<TestObject>>> getAllValues(@RequestParam Integer id,
@RequestParam List<Status> statuses,
@RequestParam(required = false, defaultValue = "false") Boolean latest) {
return ...
}
}
public interface UserConnectionAuthorizerService {
Mono<Boolean> isUserConnected(Long id);
}
When running code in the integration tests I receive:
2024-06-06 18:55:42,135 [parallel-4] ERROR o.s.b.a.w.r.e.AbstractErrorWebExceptionHandler - [5f1ceeb9-3] 500 Server Error for HTTP GET "/test/values?id=1&statuses=APPROVED"
java.lang.IllegalArgumentException: Failed to evaluate expression 'hasAuthority('ppcv:browse') || (hasAuthority('ppcv:browse-scoped') && @userConnectionAuthorizerService.isUserConnected(#id))'
at org.springframework.security.authorization.method.ReactiveExpressionUtils.lambda$evaluate$0(ReactiveExpressionUtils.java:43)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ Handler test.TestController#getAllValues(Integer, List, Boolean) [DispatcherHandler]
*__checkpoint ⇢ test.core.infrastructure.actuator.spring.DefaultActuatorWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ test.core.application.spring.http.filter.AdminSecurityFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthorizationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ExceptionTranslationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ LogoutWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ServerRequestCacheWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ UserIdAuthenticationFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HttpHeaderWriterWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP GET "/test/values?id=1&statuses=APPROVED" [ExceptionHandlingWebHandler]
Original Stack Trace:
at org.springframework.security.authorization.method.ReactiveExpressionUtils.lambda$evaluate$0(ReactiveExpressionUtils.java:43)
...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from reactor.core.publisher.MonoJust<java.lang.Boolean> to java.lang.Boolean
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:87)
at org.springframework.expression.common.ExpressionUtils.convertTypedValue(ExpressionUtils.java:57)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:222)
at org.springframework.expression.spel.ast.OpAnd.getBooleanValue(OpAnd.java:57)
at org.springframework.expression.spel.ast.OpAnd.getValueInternal(OpAnd.java:52)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:222)
at org.springframework.expression.spel.ast.OpOr.getBooleanValue(OpOr.java:56)
at org.springframework.expression.spel.ast.OpOr.getValueInternal(OpOr.java:51)
at org.springframework.expression.spel.ast.OpOr.getValueInternal(OpOr.java:37)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273)
at org.springframework.security.authorization.method.ReactiveExpressionUtils.lambda$evaluate$2(ReactiveExpressionUtils.java:39)
...
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [reactor.core.publisher.MonoJust<java.lang.Boolean>] to type [java.lang.Boolean]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:294)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:185)
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:82)
... 260 common frames omitted
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in ReactiveExpressionUtils.java, where the stack trace shows reactive method-security expressions are evaluated, and trace how a compound expression combines hasAuthority with a Mono result. Reproduce the WebFlux case with @EnableReactiveMethodSecurity and the provided @PreAuthorize expression; done means the compound expression evaluates without a ConverterNotFoundException.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100