Please remove final keyword from getAuthentication() signature so that SecurityExpressionRoot descendents can override getAuthentication() returned type with a specific Authentication implementation.

SecurityExpressionRoot, takes an Authentication as constructor param.

When providing an implementation for this abstract class, I know which type of Authentication I'm providing to parent constructor and would like to avoid a cast after each getAuthentication() call.

Comment From: rwinch

This is intended for use with SpEL. Please provide a complete example of how you are using this in a way that needs a cast.

Comment From: ch4mpy

@rwinch

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        return new GenericMethodSecurityExpressionHandler(new ExpressionRootSupplier<>(ProxiesAuthentication.class, ProxiesMethodSecurityExpressionRoot::new));
    }

    static final class ProxiesMethodSecurityExpressionRoot extends GenericMethodSecurityExpressionRoot<ProxiesAuthentication> {
        public ProxiesMethodSecurityExpressionRoot() {
            super(ProxiesAuthentication.class);
        }

        public boolean is(String preferredUsername) {
            return ((ProxiesAuthentication) getAuthentication()).hasName(preferredUsername);
        }

        public Proxy onBehalfOf(String proxiedUsername) {
            return ((ProxiesAuthentication) getAuthentication()).getProxyFor(proxiedUsername);
        }

        public boolean isNice() {
            return hasAnyAuthority("ROLE_NICE_GUY", "SUPER_COOL");
        }
    }

Taken from this small sample.

git clone https://github.com/ch4mpy/spring-addons.git
cd spring-addons/samples/tutorials/resource-server_with_specialized_oauthentication
mvn test

Should be enough to run unit test.

Please adapt application.properties to your authorization server before you run spring-boot app.

Here is where cast actually happens in the sample linked above. Please note that, thanks to <T extends Authentication> templating: - cast is rather safe as the destination type is passed as constructor parameter - getAuthentication() returning a T would not compromise "use with SpEL" as T is Authentication (it extends it).

With current SecurityExpressionRoot implementation, and provided that security-context is populated with ProxiesAuthentication from linked sample, @PreAutorize("authentication.token.preferredUsername == #username") is successfully evaluated but equivalent java code would not compile without explicit cast.

P.S.

I know about the "@Bean" alternative to MethodSecurityExpressionRoot inheritance, but definitely prefer

@PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')")

to

@PreAuthorize("authentication.claims.preferredUsername == #username or @mySecuritySpelExtensions.isNice(authentication) or @mySecuritySpelExtensions.onBehalfOf(authentication, #username).can('greet')")

Comment From: ch4mpy

@rwinch I got your point.

All the REST APIs I did recently where requiring users to be authenticated, which took me to the assomption that Authentication type can be knwon for the entire app from its web wecurity-config.

This is wrong; for instance, as soon as one exposes a public resource to anonymous (Authentication is then AnonymousAuthenticationToken) and I don't currently see other way to implement above method security handler than:

    static final class ProxiesMethodSecurityExpressionRoot extends PublicMethodSecurityExpressionRoot {

        public boolean is(String preferredUsername) {
            return Objects.equals(preferredUsername, getAuthentication().getName());
        }

        public Proxy onBehalfOf(String proxiedUsername) {
            return get(ProxiesAuthentication.class)
                    .map(a -> a.getProxyFor(proxiedUsername))
                    .orElse(new Proxy(proxiedUsername, getAuthentication().getName(), List.of()));
        }

        public boolean isNice() {
            return hasAnyAuthority("ROLE_NICE_GUY", "SUPER_COOL");
        }

        @SuppressWarnings("unchecked")
        private <T extends Authentication> Optional<T> get(Class<T> expectedAuthType) {
            return Optional
                    .ofNullable(getAuthentication())
                    .map(a -> a.getClass().isAssignableFrom(expectedAuthType) ? (T) a : null)
                    .flatMap(Optional::ofNullable);
        }
    }