My authentication filters are not firing up under request.
I have 2 security configurations, one for the login endpoint only, authenticating with the Authentic
Your filters extend UsernamePasswordAuthenticationFilter
and this filter by default is only applied for URL /login
, see UsernamePasswordAuthenticationFilter:
This filter by default responds to the URL
/login
.
If you want to change the default to another URL, see AbstractAuthenticationProcessingFilter#setFilterProcessesUrl:
Sets the URL that determines if authentication is required
Your modified code:
public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception {
AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter();
authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
authenticationFromCredentialsFilter.setFilterProcessesUrl("/api/users/login");
return authenticationFromCredentialsFilter;
}
If you want to use a pattern, see AbstractAuthenticationProcessingFilter:
This filter will intercept a request and attempt to perform authentication from that request if the request matches the
setRequiresAuthenticationRequestMatcher(RequestMatcher)
.
Your modified code:
public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception {
AuthenticationFromTokenFilter authenticationFromTokenFilter= new AuthenticationFromTokenFilter();
authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
authenticationFromTokenFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/**");
return authenticationFromTokenFilter;
}
I could finally have the filters fire up and each for their own requests under a Spring Boot 2
environment.
The credentials filter fires up upon a login request to the /users/login
endpoint.
The token filter fires up upon any request except the login request.
To assign a url pattern to a filter, the filter had to extend the AbstractAuthenticationProcessingFilter
class. And the two filters extend that class. The pattern is specified in their constructor.
The credentials filter is:
public class AuthenticationFromCredentialsFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
@Autowired
CredentialsService credentialsService;
public AuthenticationFromCredentialsFilter(final RequestMatcher requestMatcher) {
super(requestMatcher);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
CredentialsResource credentialsResource = new ObjectMapper().readValue(req.getInputStream(),
CredentialsResource.class);
return credentialsService.authenticate(credentialsResource);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authentication) throws IOException, ServletException {
tokenAuthenticationService.addTokenToResponseHeader(response, authentication);
}
}
Note that on successful authentication, the filter chain is not pursued, that is there is no such call to filterChain.doFilter(httpRequest, httpResponse);
because there is no need to hit a controller endpoint, since the response is already sent back with the token.
It is explicitly instantiated with a @Bean
annotation so as to specify the matcher pattern:
@Bean
public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception {
AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name()));
authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFromCredentialsFilter;
}
The token filter is:
public class AuthenticationFromTokenFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
public AuthenticationFromTokenFilter(final RequestMatcher requestMatcher) {
super(requestMatcher);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
return tokenAuthenticationService.authenticate(request);
}
@Override
protected void successfulAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
FilterChain filterChain, Authentication authResult) throws IOException, ServletException {
filterChain.doFilter(httpRequest, httpResponse);
}
}
It is explicitly instantiated with a @Bean
annotation so as to specify the matcher pattern:
@Bean
public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception {
AuthenticationFromTokenFilter authenticationFromTokenFilter = new AuthenticationFromTokenFilter(new NegatedRequestMatcher(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name())));
authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFromTokenFilter;
}
Contrary to the previous credentials filter, this filter needs to be explicitly specified in the configuration:
http
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
To avoid this filter being fired up twice in the filters chain, a bean instructs Spring Boot not to automatically inject it:
@Bean
FilterRegistrationBean<AuthenticationFromTokenFilter> disableAutoRegistration(final AuthenticationFromTokenFilter filter) {
final FilterRegistrationBean<AuthenticationFromTokenFilter> registration = new FilterRegistrationBean<AuthenticationFromTokenFilter>(filter);
registration.setEnabled(false);
return registration;
}
Note that on successful authentication, the filter chain is pursued, that is there is a call to filterChain.doFilter(httpRequest, httpResponse);
because there is a need to hit a controller endpoint, after successful authentication.
The full configuration is:
@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.user.rest.security",
"com.thalasoft.user.rest.service", "com.thalasoft.user.rest.filter" })
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private SimpleCORSFilter simpleCORSFilter;
@Autowired
AuthenticationFromTokenFilter authenticationFromTokenFilter;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception {
AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name()));
authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFromCredentialsFilter;
}
@Bean
public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception {
AuthenticationFromTokenFilter authenticationFromTokenFilter = new AuthenticationFromTokenFilter(new NegatedRequestMatcher(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name())));
authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFromTokenFilter;
}
@Bean
FilterRegistrationBean<AuthenticationFromTokenFilter> disableAutoRegistration(final AuthenticationFromTokenFilter filter) {
final FilterRegistrationBean<AuthenticationFromTokenFilter> registration = new FilterRegistrationBean<AuthenticationFromTokenFilter>(filter);
registration.setEnabled(false);
return registration;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(simpleCORSFilter, ChannelProcessingFilter.class);
http
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "/error").permitAll()
.antMatchers("/users/login").permitAll()
.antMatchers("/admin/**").hasRole(UserDomainConstants.ROLE_ADMIN)
.anyRequest().authenticated();
}
}
Also, note that the is not being used at all in this Spring Boot configuration:
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
CredentialsService credentialsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return credentialsService.authenticate(authentication);
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
I wonder where it could fit and how it could replace the credentials filter... but that is another story.