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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 | |
} | |
} |
Now, with a little convenience method, we can make some pretty nice Spring security configuration:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 ) | |
} |
0 comments: