We try to substitute an existing Spring Security Basic Login for a REST-API in an Open Source Application to achieve a custom login with a token. I read this blogpost about the topic: http://javattitude.com/2014/06/07/spring-security-custom-token-based-rest-authentication/
When the request has no header named "Cookie", I get correcty a 401 - unauthorized response (expected behaviour). When the request has a valid token, I get an infinite loop causing a java.lang.StackOverflowError
:
Exception in thread "http-bio-8080-exec-45" java.lang.StackOverflowError
at org.apache.tomcat.util.http.NamesEnumerator.<init>(MimeHeaders.java:402)
at org.apache.tomcat.util.http.MimeHeaders.names(MimeHeaders.java:228)
at org.apache.catalina.connector.Request.getHeaderNames(Request.java:2108)
at org.apache.catalina.connector.RequestFacade.getHeaderNames(RequestFacade.java:726)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.attemptAuthentication(CustomTokenAuthenticationFilter.java:43)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:65)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:749)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:487)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:412)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:132)
at org.activiti.rest.security.TokenSimpleUrlAuthenticationSuccessHandler.onAuthenticationSuccess(TokenSimpleUrlAuthenticationSuccessHandler.java:30)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:298)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:65)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:749)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:487)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:412)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:132)
at org.activiti.rest.security.TokenSimpleUrlAuthenticationSuccessHandler.onAuthenticationSuccess(TokenSimpleUrlAuthenticationSuccessHandler.java:30)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:298)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
My Spring Security Configuration looks like this:
@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationProvider authenticationProvider() {
return new BasicAuthenticationProvider();
}
@Autowired
AuthenticationProvider basicAuthenticationProvider;
@Bean
public CustomTokenAuthenticationFilter customTokenAuthenticationFilter(){
System.out.println("+++ create new CustomTokenAuthenticationFilter for path=/**");
return new CustomTokenAuthenticationFilter("/**");
};
@Autowired
CustomTokenAuthenticationFilter customTokenAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("init of http security START");
http
.authenticationProvider(authenticationProvider())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()//.authenticationProvider(basicAuthenticationProvider);
.addFilterBefore(customTokenAuthenticationFilter, BasicAuthenticationFilter.class)
.httpBasic();
//.and().addFilter(filter);
System.out.println("init of http security DONE");
}
}
I already tried to change the URL-Mapping from /**
to /activiti-rest/**
but then, the Basic Authentication kicks in again.
This is my custom Token Authentication filter:
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(new NoOpAuthenticationManager());
setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
}
public final String HEADER_SECURITY_TOKEN = "Cookie";//"LdapToken";
/**
* Attempt to authenticate request - basically just pass over to another method to authenticate request headers
*/
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Enumeration<String> headerNames = request.getHeaderNames();
int i = 0;
while (headerNames.hasMoreElements()){
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
System.out.println("+++ key["+i+"]" +key);
System.out.println("+++ val["+i+"]" +value);
i++;
}
String token = request.getHeader(HEADER_SECURITY_TOKEN);
logger.info("token found:"+token);
System.out.println("+++ token found:"+token);
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
System.out.println("+++ userAuthenticationToken:"+userAuthenticationToken.toString());
return userAuthenticationToken;
}
/**
* authenticate the user based on token
* @return
*/
private AbstractAuthenticationToken authUserByToken(String token) {
if(token==null) {
System.out.println("+++ i shouldn't be null +++");
return null;
}
AbstractAuthenticationToken authToken = new JWTAuthenticationToken(token);
try {
return authToken;
} catch (Exception e) {
System.out.println(e);
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
System.out.println("++++++++++++++++++++++++++++++ doFilter ");
super.doFilter(req, res, chain);
}
}
And my Custom Success handler. I think that this causes the infinite loop, but I cannot figure out, why:
public class TokenSimpleUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response) {
System.out.println("+++ yuhuuu determineTargetUrl+++");
String context = request.getContextPath();
String fullURL = request.getRequestURI();
String url = fullURL.substring(fullURL.indexOf(context)+context.length());
return url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println("+++ yuhuuu onAuthenticationSuccess+++");
String url = determineTargetUrl(request,response);
request.getRequestDispatcher(url).forward(request, response);
}
}
All other classes (NoOpAuthenticationManager and RestAuthenticationEntryPoint) are exactly like in this blogpost.
Would be great I someone could give me a hint what could cause this infinite loop. As I said, it only occurs when the Request has a valid token.
Thanks and best regards Ben