I'm trying to configure my WebSecurityConfigurerAdapter to be able to authenticate two different (and incompatible) authentication flows. To make it simple, the requests sent to the server can have two possible type of token in the header, each type has its own header key (ex: 'webAuth' and 'hardwareAuth').
Rigth now, I have an AuthenticationProvider and ProcessingFilter for each flow.
I set up my configuration as follow:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(unauthorizedEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(webAuthenticationProvider)
.authenticationProvider(hardwareAuthenticationProvider)
.addFilterBefore(webAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.addFilterBefore(hardwareAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(ANT_MATCHES).not().authenticated()
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable();
}
webAuthenticationProvider
is executed before hardwareAuthenticationProvider
.
The problems are the following:
I'd like to introduce a mechanism that allow me to either perform correctly both authentication or to only execute the 'correct' one programmatically checking the header beforhand.
Here the implementation of my providers:
@Component
public class HardwareAuthenticationProvider implements AuthenticationProvider {
private HardwareTokenService hardwareTokenService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String vToken = (String) authentication.getCredentials();
if (hardwareTokenService.auth(vToken)) {
UserDetails userDetails = new User("hw", "", new ArrayList<>());
return new UsernamePasswordAuthenticationToken(userDetails, vToken, userDetails.getAuthorities());
} else {
throw new BadCredentialsException("Invalid v-token");
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@RequiredArgsConstructor
@Component
public class TokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@NonNull
private AuthenticationService authenticationService;
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object token = authentication.getCredentials();
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object token = authentication.getCredentials();
return Optional
.ofNullable(token)
.map(t -> authenticationService.loadUserByToken(String.valueOf(t)))
.orElseThrow(() -> new BadCredentialsException("Invalid authentication token=" + token));
}
}
As stated create your own custom Authentication
object, or at least for the hardware one.
public class HardwareAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private Object credentials;
public HardwareAuthenticationToken(String token) {
super(null);
this.credentials = token;
}
public HardwareAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
public String getToken() {
return (String) this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated,
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
Now modify the HardwareAuthenticationProvider
to use this specific token.
@Component
public class HardwareAuthenticationProvider implements AuthenticationProvider {
private HardwareTokenService hardwareTokenService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String vToken = authentication.getToken();
if (hardwareTokenService.auth(vToken)) {
UserDetails userDetails = new User("hw", "", new ArrayList<>());
return new HardwareAuthenticationToken(userDetails, vToken, userDetails.getAuthorities());
} else {
throw new BadCredentialsException("Invalid v-token");
}
}
@Override
public boolean supports(Class<?> authentication) {
return HardwareAuthenticationToken.class.isAssignableFrom(authentication);
}
}
In your HardwareAuthenticationFilter
construct the HardwareAuthenticationToken
using the single arg constructor instead of creating a UsernamePasswordAuthenticationToken
.
You could do the same for the web based authentication and create a dedicated subclass of the UsernamePasswordAuthenticationToken
like WebAuthenticationToken
and let the other provider react to that specifically.