Spring security & subdomains

As previously mentioned, I have been working with a Spring MVC app that has had to deal with multiple subdomains for the one app (in other words, the subdomain really needs to just be considered as part of the normal URL path in all routing/security configuration and concerns).

Having gone through the details on how to make the @Controller and @RequestMapping routing to play nicely with subdomains, here is a quick overview of how to handle subdomains in Spring security.


A custom matcher

The main thing we really need to handle with security, is how to configure Spring-security so we can define permissions for URLs that include the subdomain.

Normally, Spring MVC permissions looks something like this:
http.authorizeRequests()
.antMatchers("/welcome").permitAll()
.antMatchers("/dashboard/**").authenticated()

As you can see, this just specifies a URL path to authenticate.


The specific details of how you implement the matcher exactly will be dependent on your applications approach to identifying and extracting the subdomain (maybe from http request, maybe just use a regex on the request etc)

@Slf4j
class SubdomainAndPathSecurityMatcher implements RequestMatcher {
RequestHeaderRequestMatcher subdomainMatcher
AntPathRequestMatcher pathMatcher
private String sub
private String path
public SubdomainAndPathSecurityMatcher( String subdomain, String antPath ){
//subdomainMatcher = Specific matcher based on your subdomain identification approach
//e.g. it might be passed in a header like: new RequestHeaderRequestMatcher( "subdomain", subdomain )
//or it might just be: new RegexRequestMatcher( subdomain, "GET") -
//http://docs.spring.io/autorepo/docs/spring-security/3.2.5.RELEASE/apidocs/org/springframework/security/web/util/RegexRequestMatcher.html
pathMatcher = new AntPathRequestMatcher( antPath )
sub = subdomain
path = antPath
}
@Override public boolean matches(HttpServletRequest request) {
log.trace( "Checking security subdomain matcher:: Looking for subdomain ${sub} and path ${path} " )
boolean result = (subdomainMatcher.matches( request ) && pathMatcher.matches( request ) )
log.trace( "Checking security subdomain matcher:: Match found = ${result} " )
result
}
}
As you can see above, the matcher we have created is just a convenient wrapper around another two spring matchers to let you match easily on both the full URL and subdomain.

Now, with a little convenience method, we can make some pretty nice Spring security configuration:

@Configuration
@EnableWebSecurity
class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers( subdomainAndPath("users", "/dashboard/**") ).hasRole( "USER" )
.requestMatchers( subdomainAndPath("admin", "/dashboard/**") ).hasRole( "ADMIN" )
.anyRequest().permitAll()
//more security config..
}
//a convenient helper method to make our config a bit neater and nicer to read
private SubdomainAndPathSecurityMatcher subdomainAndPath( String subdomain, String path ){
new SubdomainAndPathSecurityMatcher( subdomain, path )
}
As you can see, the subdomain makes a difference to the permissions and who should access the two /dashboard/ URLs in the different contexts, but with the above simple code, we can make some pretty convenient & readable configuration to take subdomains into account.

0 comments: