2

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() {
Greg
  • 1,227
  • 5
  • 23
  • 52

1 Answers1

1

It is not possible to define GlobalSecurityConfiguration in a reactive Spring application (current Spring Security version 5.5.2).

In order to have the same functionality, you need to replace methodSecurityExpressionHandler defined in ReactiveMethodSecurityConfiguration with your own method security expression handler.

To do that, you can extend DefaultMethodSecurityExpressionHandler and define it as @Primary bean.
For your case, it is going to be as following.

public class CustomMethodSecurityExpressionHandler extends 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());
    root.setDefaultRolePrefix(getDefaultRolePrefix());
    return root;
  }
}

@Bean
@Primary
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
  return new CustomMethodSecurityExpressionHandler();
}

References:

feser
  • 41
  • 3