Reputation: 10716
Right now, with Spring Security's HttpSecurity
, we're able to restrict wildcard paths to specific roles/authorities:
.mvcMatchers(POST, "/users").hasAuthority("create:users")
.mvcMatchers(PUT, "/users/{id}").hasAuthority("update:users")
is there an easy way to do:
.mvcMatchers(POST, "/{whateverGoesHere}").hasAuthority("create:${whateverGoesHere}")
.mvcMatchers(PUT, "/{whateverGoesHere}/{id}").hasAuthority("update:${whateverGoesHere}")
?
It doesn't have to be a solution using the configure(HttpSecurity http)
API specifically, I'm just looking for an easy way to generify authorization rules for multiple REST entities at once.
Upvotes: 1
Views: 1001
Reputation: 6158
This is obviously a more advanced scenario, to say the least. However, improvements in Spring Security 5.5 have introduced the new AuthorizationManager
interface and the http.authorizeHttpRequests()
method for configuring authorization rules that utilize it. See The AuthorizationManager in the reference docs for more info. It is extremely powerful! I believe this is probably the best option for your use case.
There are numerous implementations available in Spring Security that can be used to build composite and/or delegating implementations. Here's an example that uses your convention:
public final class ResourceAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final String action;
public ResourceAuthorizationManager(String action) {
this.action = action;
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
AuthorizationManager<RequestAuthorizationContext> delegate =
AuthorityAuthorizationManager.hasAuthority(createAuthority(context));
return delegate.check(authentication, context);
}
private String createAuthority(RequestAuthorizationContext context) {
String resource = context.getVariables().get("resource");
return String.format("%s:%s", this.action, resource);
}
}
The action
can be create
, read
, update
, delete
or anything you like as part of your authority string. This implementation relies on URI variables provided through the RequestAuthorizationContext
. As it happens, there's an existing implementation (RequestMatcherDelegatingAuthorizationManager
) that handles that scenario. It is actually the one handling .mvcMatchers()
authorization rules in the Spring Security DSL. Here's an example that uses it to delegate to the convention-based AuthorizationManager
above:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.mvcMatchers(HttpMethod.POST, "/{resource}").access(new ResourceAuthorizationManager("create"))
.mvcMatchers(HttpMethod.PUT, "/{resource}/{id}").access(new ResourceAuthorizationManager("update"))
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
Upvotes: 3
Reputation: 688
I think that you should not have hidden behavior in your code. If a developer wants to add a new endpoint and wants to have it require some authority, it should be done intentionally. Otherwise, it could become a debugging nightmare if the dev intends to add an open endpoint and wonders why it is secured.
But you could add a default behavior for all endpoints that you did not specify. That behavior could be to deny access. That way, every developer has to add some kind of access granting entry. That would guarantee that it is not forgotten, but it is still intentionally done.
...
.mvcMatchers(POST, "/users").hasAuthority("create:users")
.mvcMatchers(PUT, "/users/{id}").hasAuthority("update:users")
.anyRequest().denyAll()
Upvotes: 0