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");
}
}
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);
}
}