Open bskorka opened 3 months ago
@bskorka, thanks for the report. Will you please try the following?
Change @PreAuthorize
to reference only one bean like so:
@PreAuthorize("@authz.checkEverything(#root)"`)
Then, introduce the above bean in such a way that it checks all three conditions. Can you confirm that this allows you to return Mono<Boolean>
as expected?
In the meantime, I will take a look and see what can be done about supporting expression elements that return a mixture of Boolean
and Mono<Boolean>
. I'm not convinced that this should be added, but it's worth a look. And, we can at least try and improve the error message with additional context.
Hey @jzheaux!
I can confirm that moving all the expressions to one method returning the Mono<Boolean>
works fine, as I have used it since support for Mono<Boolean>
was introduced in Spring Security 5.8.9(?).
The implementation of this approach looks like this:
public class TestController {
@GetMapping("/values")
@PreAuthorize("@userConnectionAuthorizerService.hasUserAccessToConnectionData(#id))")
public Mono<ApiResponse<List<TestObject>>> getAllValues(@RequestParam Integer id,
@RequestParam List<Status> statuses,
@RequestParam(required = false, defaultValue = "false") Boolean latest) {
return ...
}
}
public class UserConnectionAuthorizerServiceImpl implements UserConnectionAuthorizerService {
private final PreAuthService preAuthService;
@Override
public Mono<Boolean> hasUserAccessToConnectionData(Long id) {
return Mono.zip(
preAuthService.hasAuthority(Permissions.PPCV_BROWSE.getValue()),
preAuthService.hasAuthority(Permissions.PPCV_BROWSE_SCOPED.getValue())
)
.flatMap(tuple -> tuple.getT1() ? Mono.just(true) :
tuple.getT2() ? checkUserConnection(id) : // business check, same as isUserConnected in previous example
Mono.just(false)
)
.switchIfEmpty(Mono.just(false));
}
}
public class PreAuthServiceImpl implements PreAuthService {
public Mono<Boolean> hasAuthority(String authority) {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(authentication -> authentication.getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)));
}
}
I understand that handling the mix of Boolean
and Mono<Boolean>
might not be easy to implement understandably and satisfyingly. Maybe a reactive variant of the @PreAuthorize
annotation or reactiveHasAuthority
returning the Mono<Boolean>
will work?
I agree that the error message can be improved as I spent some time thinking that the issue is related to my reactive pipe.
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:However, if we want to use the
@PreAuthorize
connected with aMono<Boolean>
andhasAuthority
we are getting an errorNo converter found capable of converting from type [reactor.core.publisher.MonoFlatMap<java.lang.Boolean, ?>] to type [java.lang.Boolean]
To Reproduce
@EnableReactiveMethodSecurity
configuration@PreAuthorize
usageMono<Boolean>
and call the endpoint - it works@PreAuthorize
- the exception will be thrown on callExpected behaviour The "complex"
@PreAuthorize
expression should be handled without isues if we use methods returningMono<Boolean>
and built-inhasAuthority
calls.Sample
When running code in the integration tests I receive: