Spring Boot: inject custom context path
I am running a Spring Boot 1.2.3 application with Tomcat embedded.
I would like to add a custom contextPath for each request based on the first part of the url.
Examples:
-
http://localhost:8080/foo
has a defaultcontextPath=""
and should getcontextPath="foo"
-
http://localhost:8080/foo/bar
has a defaultcontextPath=""
and should getcontextPath="foo"
(URLs without path should stay as they are)
I tried to write a user javax.servlet.Filter
with @Order(Ordered.HIGHEST_PRECEDENCE)
, but it looks like I missed something. Here's the code:
@Component @Order(Ordered.HIGHEST_PRECEDENCE)
public class MultiTenancyFilter implements Filter {
private final static Pattern pattern = Pattern.compile("^/(?<contextpath>[^/]+).*$");
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest) request;
final String requestURI = req.getRequestURI();
Matcher matcher = pattern.matcher(requestURI);
if(matcher.matches()) {
chain.doFilter(new HttpServletRequestWrapper(req) {
@Override
public String getContextPath() {
return "/"+matcher.group("contextpath");
}
}, response);
}
}
@Override public void init(FilterConfig filterConfig) throws ServletException {}
@Override public void destroy() {}
}
It just needs to take the String after the first /
and before (if any) the second /
, and then use that as the return value for getContextPath()
.
But Spring @Controller @RequestMapping and Spring Security antMatchers("/")
doesn't seem to respect it. Both still work as contextPath=""
.
How can I dynamically override the context path for each request?
source to share
It worked!
The Spring Security Docs ( http://docs.spring.io/spring-security/site/docs/3.1.x/reference/security-filter-chain.html ) says "Spring Security is only interested in providing paths in the application, so contextpath is ignored Unfortunately, the servlet specification does not specify exactly what servletPath and pathInfo values will contain for a particular request URI. [...] The strategy is implemented in the AntPathRequestMatcher class, which uses Spring's AntPathMatcher to perform case insensitive pattern matching with the concatenated servletPath and pathInfo, ignoring queryString. "
So, I just overridden servletPath
and contextPath
(even if not used by Spring Security). Also, I added a little redirect because normally when clicked http://localhost:8080/myContext
you are redirected to http://localhost:8080/myContext/
and Spring Securities Ant Matches didn't like the missing trailing slashes.
So here is my code MultiTenancyFilter
:
@Component @Order(Ordered.HIGHEST_PRECEDENCE)
public class MultiTenancyFilter extends OncePerRequestFilter {
private final static Pattern pattern = Pattern.compile("^(?<contextPath>/[^/]+)(?<servletPath>.*)$");
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Matcher matcher = pattern.matcher(request.getServletPath());
if(matcher.matches()) {
final String contextPath = matcher.group("contextPath");
final String servletPath = matcher.group("servletPath");
if(servletPath.trim().isEmpty()) {
response.sendRedirect(contextPath+"/");
return;
}
filterChain.doFilter(new HttpServletRequestWrapper(request) {
@Override
public String getContextPath() {
return contextPath;
}
@Override
public String getServletPath() {
return servletPath;
}
}, response);
} else {
filterChain.doFilter(request, response);
}
}
@Override
protected String getAlreadyFilteredAttributeName() {
return "multiTenancyFilter" + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX;
}
}
It just fetches the contextPath and servletPath using the url scheme I specified here: https://theholyjava.wordpress.com/2014/03/24/httpservletrequest-requesturirequesturlcontextpathservletpathpathinfoquerystring/
Also, I had to provide my own method getAlreadyFilteredAttributeName
, because otherwise the filter was called twice. (This resulted in deletion contextPath
twice)
source to share