In your latest comment you described using only MySQL, and I think that makes sense. I don't see Redis fitting in anywhere in this scenario. So the flow is:
- In authorization server, you use
OAuth2TokenCustomizer<JwtEncodingContext>
to add the authority
field to the JWT (e.g. ROLE_xyz
).
- In a resource server, you use
JwtGrantedAuthoritiesConverter
to map the authority
field of the JWT to authentication.getAuthorities()
in Spring Security.
The challenge is that in your DB, you're mapping roles directly to a set of allowed URLs (e.g. ROLE_xyz
=> POST /customer
, ...). Perhaps you're dealing with an existing database schema and can't design the data or relationships from scratch. What I would expect is a mapping of roles to a set of permissions (e.g. ROLE_xyz
=> customer.write
, ...).
If you have a permissions scheme like this, you could do the lookup in the JwtGrantedAuthoritiesConverter
above and map those permissions as authorities instead.
However, if the data in MySQL maps roles to URL patterns, your best bet is to use the AuthorizationManager
API to integrate authorization with your MySQL table (see Configure RequestMatcherDelegatingAuthorizationManager under Authorize HttpServletRequests with AuthorizationFilter for an example).
A custom AuthorizationManager
can be implemented to look up ROLE_xyz
and dynamically build a list of authorization rules with RequestMatcherDelegatingAuthorizationManager.builder()
.
For example:
@Configuration
@EnableWebMvc
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)
throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().access(access)
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
}
@Bean
AuthorizationManager<RequestAuthorizationContext> authorizationManager(
HandlerMappingIntrospector introspector) {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
RequestMatcher anyRequest = AnyRequestMatcher.INSTANCE;
AuthorizationManager<RequestAuthorizationContext> authenticated =
AuthenticatedAuthorizationManager.authenticated();
AuthorizationManager<RequestAuthorizationContext> denyAll =
(a, c) -> new AuthorizationDecision(false);
return (supplier, context) -> {
Authentication authentication = supplier.get();
HttpServletRequest request = context.getRequest();
// TODO: Look up mappings in MySQL using authentication.getAuthorities()...
RequestMatcherDelegatingAuthorizationManager.Builder builder =
RequestMatcherDelegatingAuthorizationManager.builder();
// TODO: Add custom mappings from MySQL...
//builder.add(mvcMatcherBuilder.pattern("/customer/**"), authenticated);
RequestMatcherDelegatingAuthorizationManager delegate =
builder.add(anyRequest, denyAll).build();
return delegate.check(() -> authentication, request);
};
}
}
This may seem complex, but your authorization scheme (using OAuth2 + JWTs AND roles/authorities AND MySQL to manage roles) is a somewhat complex setup. The AuthorizationManager
API actually makes it easier than it has been in earlier versions of Spring Security.
Also note that the above example assumes that role mappings in the database can change at runtime, and so must be looked up every time. If this is not the case, the example could be changed to load data from MySQL only on startup. MySQL lookup performance could also be improved with caching if needed.