How to require multiple roles/authorities
Asked Answered
R

6

20

As far as I can tell only any of lists are available in @Secured annotations or ExpressionUrlAuthorizationConfigurer objects. Trying to add multiple annotations or hasAuthority() calls either fails to compile or only the latest one is used.

How can I define that a particular request (set of requests matching a pattern), or method requires all of a list of roles/authorities?

Radar answered 4/2, 2016 at 15:12 Comment(7)
hasAuthority() and hasAuthority() and hasAuthority() for @Secured and access() when using the ExpressionUrlAuthorizationConfigurer.Christoper
You mean @PreAuthorise, not @Secured?Radar
The problem with that is there's no way to add to the SpEL context, so you can't use constants specifying the names.Radar
Well no,but why would you want a constant?Christoper
Why does anyone want hard-coded strings used in multiple places in constants? For reduced bugs and better maintainability.Radar
And how would that lead to less bugs, you would need the same amount of static references as roles.Christoper
You can't spell it wrong, for example.Radar
R
23

The best solution appears to be

@PreAuthorize("hasRole('one') and hasRole('two') and ...")

There's no nice way to use constants, like with @Secured.

Radar answered 31/10, 2018 at 17:33 Comment(2)
In my project I use hasAuthority instead of hasRole, and this works for authorities as well (just change Role for Authority): @PreAuthorize("hasAuthority('one') and hasAuthority('two') and ...") For any of you can change and for orWhitver
@Whitver hasRole('x') <=> hasAuthority('ROLE_x')Radar
A
4

You seem to be using: hasAuthority([authority]). This only takes one authority. Instead use hasAnyAuthority([authority1,authority2]). This allows you to specify multiple authorities at once and any can be considered in authorization. Reference official spring docs here. Just find in page the text: hasAnyAuthority([authority1,authority2])

For example on your controller method, add: @PreAuthorize("hasAnyAuthority('permission1','permission2')")

Aleida answered 31/10, 2018 at 17:22 Comment(1)
As stated in the question, that's any of, not all of.Radar
L
4

From Spring Security 5.8, Spring has provided support for AuthorizationManager composition with the AuthorizationManagers class.

With this, we can define the HttpSecurity as follows:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {        
    http.authorizeHttpRequests(authorize -> authorize
          .requestMatchers(HttpMethod.GET, "/my-endpoint")
            .access(AuthorizationManagers.allOf(
              AuthorityAuthorizationManager.hasAuthority(("SCOPE_one")),
              AuthorityAuthorizationManager.hasAuthority("SCOPE_two")))
          .anyRequest()
            .authenticated());
    return http.build();
}

Of course, make use of static imports to present the configuration in a cleaner manner, I included the classes here for clarity.

Liturgical answered 22/3, 2023 at 15:39 Comment(0)
C
1

As an endpoint-wide solution, you can just use

.antMatchers("/user/**").access("hasAuthority('AUTHORITY_1') and hasAuthority('AUTHORITY_2')")

I only tested it with two authorities, but I suppose you can and- more than two.

Crews answered 16/3, 2021 at 15:5 Comment(1)
It seems with the new authorizeHttpRequests, there is no access method that supports passing a String as paramLiturgical
O
1

With Spring Security 5.5 you can use a custom AuthorizationManager when using the AuthorizeHttpRequestsConfigurer.

public class DelegatingMultiAuthorizationManager<T> implements AuthorizationManager<T> {
    private final List<AuthorizationManager<T>> authorizationManagers;

    public DelegatingMultiAuthorizationManager(List<AuthorizationManager<T>> authorizationManagers) {
        this.authorizationManagers = authorizationManagers;
    }

    public static <T> DelegatingMultiAuthorizationManager<T> hasAll(AuthorizationManager<T>... authorizationManagers) {
        Assert.notEmpty(authorizationManagers, "authorizationManagers cannot be empty");
        Assert.noNullElements(authorizationManagers, "authorizationManagers cannot contain null values");
        return new DelegatingMultiAuthorizationManager(Arrays.asList(authorizationManagers));
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
        for (AuthorizationManager<T> authorizationManager : authorizationManagers) {
            AuthorizationDecision decision = authorizationManager.check(authentication, object);
            if (decision == null || !decision.isGranted()) {
                return new AuthorizationDecision(false);
            }
        }

        return new AuthorizationDecision(true);
    }
}

This AuthorizationManager requires each provided AuthorizationManager to grant access. If any of them does not grant access the authorization will fail.

You can then use it like this (which also allows using constants):

http.authorizeHttpRequests(authorize -> authorize
            .antMatchers("/something/*")
                .access(DelegatingMultiAuthorizationManager
                                .hasAll(AuthorityAuthorizationManager.hasAuthority("auth_a"),
                                        AuthorityAuthorizationManager.hasAuthority("auth_b")))
Orchid answered 11/1, 2023 at 13:1 Comment(2)
I assume that doesn't work in annotations though?Radar
No, as others have said you can use hasAuthority(..) and hasAuthority(..) for annotations. If you want to further use constants in your annotations you'd need to either use the full name (for example hasAuthority(T(com.my.package.Constants).AUTH_A)) or use a bean reference (for example hasAuthority(@bean.AUTH_A))Orchid
M
-2

You can use hasAnyAuthority as like .hasAnyAuthority("manager", "customer");

Maurine answered 11/10, 2021 at 0:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.