Open
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:
- ReactiveAuthorizationManager + Reactive Method Security #9401
- Support Mono<Boolean> for Method Security SpEL expressions #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
@EnableReactiveMethodSecurity
configuration - Define Controller with a
@PreAuthorize
usage - 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