Spring WebFlux Security PreAuthorize Best Practice

Viewed 44

as the title suggests, I have configured security in my Spring WebFlux application by using @EnableWebFluxSecurity and @EnableReactiveMethodSecurity.

I am using RouterFunction to handle the request routing. The following code is for the router:

@Component
public class UserServiceRequestRouter {

    @Autowired
    private UserServiceRequestHandler requestHandler;

    @Bean
    public RouterFunction<ServerResponse> route() {
        //@formatter:off
        return RouterFunctions
            .route(GET("/user/{userId}"), requestHandler::getUserDetails);
        //@formatter:on
    }
}

And the request handler is:

@Component
public class UserServiceRequestHandler {

    @Autowired
    private UserService userService;

    @PreAuthorize("@userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
    public Mono<ServerResponse> getUserDetails(ServerRequest request) {
        //@formatter:off
        return userService.getUserDetails(request.pathVariable("userId"))
            .convert()
            .with(toMono())
            .flatMap(
                (UserDetails userDetails) -> ServerResponse.ok()
                    .contentType(APPLICATION_NDJSON)
                    .body(Mono.just(userDetails), UserDetails.class)
            );
        //@formatter:on
    }
}

Note: The @Autowired UserService is to fetch data from the database in a reactive way.

Next, I have defined a @Component as:

@Component
@SuppressWarnings("unused")
@Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {

    public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
        // @formatter:off
        if (authentication == null) {
            return false;
        }

        Collection<String> roles = authentication.getAuthorities()
            .stream()
            .map(Objects::toString)
            .collect(Collectors.toSet());

        if (roles.contains("Admin")) {
            return true;
        }

        Jwt principal = (Jwt) authentication.getPrincipal();
        String subject = principal.getSubject();
        String userId = request.pathVariable("userId");

        return Objects.equals(subject, userId);
        // @formatter:on
    }
}

It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.

The application is working as per the expectation. But I am wondering if I am doing it the right way, meaning is this the best practice of doing method level Authorization in a reactive way?

The followings are my stack:

  • JDK 17
  • org.springframework.boot:3.0.0-M4
  • org.springframework.security:6.0.0-M6

Any advice you could give would be much appreciated.

Update

As mentioned by M. Deinum in the comment why shouldn't I use hasAuthority("Admin") or principal.subject == #userId, the reason is that the authorization code I provided is merely for demonstration purposes. It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.

Also the question is not about using inline SpEL, it's more about its reactiveness. I don't know if the SpEL mentioned in the @PreAuthorize is reactive! If it is reactive by nature then I can assume any expression mentioned in the @PreAuthorize would be evaluated reactively.

1 Answers

As far as I know, SpEL expressions evaluation is synchronous.

Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.

Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).

Side notes

It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.

I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter. You could configure any AbstractAuthenticationToken instead, as I do in this tutorial.

It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.

I join @M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.

If you refer to the tutorial already linked above, it is possible to enhance security DSL and write stuff like: @PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give @PreAuthorize("is(#userId) or isAdmin()).

Related