I have overridden SecurityExpressionRoot
in my project exposing a method verifying whether the current user has rights to a given resource.
Then I have overriden GlobalMethodSecurityComfiguration.createExpressionHandler() and then createSecurityExpressionRoot() returning the instance of the overriden SecurityExpressionRoot. This has worked in a servlet scenario, unfortunately, this doesn't seem to work in a reactive scenario.
How do I convert the method security setup below to the reactive scenario?
In my tests I get the following error:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [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.ui.LogoutPageGeneratingWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.csrf.CsrfWebFilter [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 ⇢ HTTP GET "/things/1/jobs/1/log" [ExceptionHandlingWebHandler]
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @RequiredArgsConstructor public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { private final ThingsRepository thingsRepository;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler() {
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
DeployPermissionSecurityExpressionRoot root = new ThingsPermissionSecurityExpressionRoot(thingsRepository, authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
};
}
My security config:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt(jwt ->{
jwt.jwtDecoder(jwtDecoder());
jwt.jwtAuthenticationConverter(customJwtAuthConverter());
})
.and()
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
// @formatter:on
return http.build();
}
SecurityExpressionRoot implementation:
class ThingsPermissionSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private final ThingsRepository ThingsRepository;
private Object filterObject;
private Object returnObject;
private Object target;
ThingsPermissionSecurityExpressionRoot(ThingsRepository thingsRepository, Authentication authentication) {
super(authentication);
this.thingsRepository = thingsRepository;
}
public boolean hasThingsWritePrivilege(Long thingsId) {
Controller:
public class ThingsController {
private final JobPublisherProvider jobPublisherProvider;
@GetMapping("{thingsId}/jobs/{jobId}/log")
@PreAuthorize("hasThingsWritePrivilege(#thingsId)")
public Flux<DataBuffer> retrieveJobLog(@PathVariable String thingsId, @PathVariable int jobId) {
Controller test method
@Test
@WithMockUser(roles = {"ROLE_JAR_W"})
public void logValueProperlyRetrieved() {