In spring security, I created my own customAuthenticationprovider. when i call auth.authenticationProvider(customAuthenticationProvider) it should call the authenticate() present inside the customAuthenticationprovider class right. But there is no such flow i am able to figure out where it is calling the authenticate method.
All i want to know how authenticate() method get called. I know it is authenticationProvider which is making call to authenticate but how and where is the code?
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthenticationProvider);
}
My
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
UserDetails ud = userDetailsService.loadUserByUsername(username); // our own userDetailService is being used.
if (ud != null) {
if (passwordEncoder.matches(ud.getPassword(), password)) { // our own passwordEncoder is being used.
var a = new UsernamePasswordAuthenticationToken(username, password, ud.getAuthorities());
return a;
}
}
throw new BadCredentialsException("Error!");
}
#Override
public boolean supports(Class<?> authType) {
return UsernamePasswordAuthenticationToken.class.equals(authType);
}
}
Related
I have a Spring boot project which runs authentication with spring oauth2 token provider.
Now there is a idea to support a autentication with Keycloak, so that username and password will be stored in Keycloak and it will provide the access token.
Idea is to keep the oauth token store and provider, and as well to have a keycloak one, but to keep the roles and acces right part in spring. Keycloak will only be used for some users as a token provider instead of Spring one and to have a refresh token. So all the user data and access rights and roles are still be done by spring, from database, only the part where it authenticates username and password will be in Keycloak which provides a token.
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
private AccessDecisionManager accessDecisionManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().accessDecisionManager(accessDecisionManager)
.antMatchers("/service/*").fullyAuthenticated()
.anyRequest().permitAll().and().httpBasic().and().csrf().disable();
}
#Override
#Bean(name = "authenticationManagerBean")
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore) {
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
#Bean
public AffirmativeBased accessDecisionManager() {
List<AccessDecisionVoter<?>> accessDecisionVoters = new ArrayList<>();
accessDecisionVoters.add(new ScopeVoter());
accessDecisionVoters.add(new RoleVoter());
accessDecisionVoters.add(new AuthenticatedVoter());
AffirmativeBased accessDecisionManager = new AffirmativeBased(accessDecisionVoters);
return accessDecisionManager;
}
And there is a Custom client service which grants access rights:
#Component
public class CustomClientService implements ClientDetailsService {
private static Map<String, BaseClientDetails> cache = new ConcurrentHashMap<>();
#Autowired
UserService userService;
#Autowired
AccessRightsService accessRightsService;
#Override
public ClientDetails loadClientByClientId(String paramString) throws ClientRegistrationException {
...
Also there is a custom TokenStore class:
public class MyTokenServices extends DefaultTokenServices {
private static Logger log = LoggerFactory.getLogger(MyTokenServices.class);
public UserService userService;
public AccessRightsService accessRightService;
private TokenStore my_tokenStore;
#Override
public void setTokenStore(TokenStore tokenStore) {
// TODO Auto-generated method stub
super.setTokenStore(tokenStore);
my_tokenStore = tokenStore;
}
#Override
#Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken retVal= super.createAccessToken(authentication);
if(retVal instanceof DefaultOAuth2AccessToken) {
DefaultOAuth2AccessToken defRetVal = (DefaultOAuth2AccessToken)retVal;
log.info("New loging request"+ defRetVal.toString());
// defRetVal.setExpiration( Date.from(LocalDateTime.now().plus(8,ChronoUnit.HOURS).atZone(ZoneId.systemDefault()).toInstant()));
my_tokenStore.storeAccessToken(defRetVal, authentication);
}
return retVal;
}
#Override
public OAuth2Authentication loadAuthentication(String accessTokenValue)
throws AuthenticationException, InvalidTokenException {
OAuth2Authentication retVal = super.loadAuthentication(accessTokenValue);
OAuth2Request oldRequest = retVal.getOAuth2Request();
User user = userService.getUserByUsername(oldRequest.getClientId());
if(changeAutheticator(retVal, user)) {
HashSet<GrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(a->authorities.add(new SimpleGrantedAuthority(a.getRoleName())));
Set<String> accessRights = accessRightService.getUserAccessRights(user);
if(accessRights != null) {
accessRights.forEach(rihgt->{
authorities.add(new SimpleGrantedAuthority(rihgt));
});
}
OAuth2Request newRequest = new OAuth2Request(retVal.getOAuth2Request().getRequestParameters(),
oldRequest.getClientId(), authorities, oldRequest.isApproved(), oldRequest.getScope(),
oldRequest.getResourceIds(), oldRequest.getRedirectUri(), oldRequest.getResponseTypes(), oldRequest.getExtensions());
retVal = new OAuth2Authentication(newRequest, retVal.getUserAuthentication());
}
return retVal;
}
/**
* Method that check do we need to change authenticator
* #param retVal
* #param user
* #return
*/
private boolean changeAutheticator(OAuth2Authentication auth, User user) {
if(user == null) return false;
if(user != null ) {
if(user.getRoles() != null) {
if(auth.getOAuth2Request()!=null && auth.getOAuth2Request().getAuthorities() != null){
for(Role role:user.getRoles()){
if(!auth.getOAuth2Request().getAuthorities().stream().anyMatch(a->a.getAuthority().equals(role.getRoleName()))){
return true;
}
}
for(GrantedAuthority ga : auth.getOAuth2Request().getAuthorities()) {
if(!user.getRoles().stream().anyMatch(a->a.getRoleName().equals(ga.getAuthority()))){
return true;
}
}
}
}
}
return false;
}
I was trying to implement a multiple authentiaction like the one from other stackoverflow but that was not a solution. Thinking that I should provide a custom authentication provider with Keycloak or still like not having a solution in head.
I use Keycloak to federate other identities: the only issuers that clients and resource-servers trust are Keycloak instances or realms. Other identity sources are hidden behind it.
With that config, roles are put into tokens by Keycloak, just as any other claims (for the last project I worked on, roles referential is LDAP but it could be a custom database table or Keycloak default one).
It's pretty easy to connect Keycloak to your user database and it comes with many features that I don't want to code and maintain (multi-factor authentication, social login, users, clients and resource-servers management screens,...)
I have created an OAuth Authorization Server using default spring boot configurations, where the client is redirected to the auto-generated login page, userDetailsService looks up the User table and authenticates, and after successful authentication the server returns a jwt token. Now I want customize this and change two things but I am having difficulty in doing it.
1) Use my own login.jsp page instead of the auto-generated login page so I can have an extra field(eg. dropdownlist) and use this along with the username and password for authentication since I have different user tables
2) Instead of using the default UserDetailsService I am trying to implement my own AuthenticationProvider, this is because I have multiple users table and want to search for user in the correct table based on the value from the extra field (dropdown list mentioned in 1). Also how to get the dropdownlist value in the AuthenticationProvider?
In my properties file I have set:
spring.mvc.view.prefix: /WEB-INF/jsp/ and
spring.mvc.view.suffix: .jsp
#Configuration
#EnableWebSecurity
public class ServerWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new DefaultAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
#Import(ServerWebSecurityConfig.class)
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
#Qualifier("dataSource")
private DataSource dataSource;
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter()).authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
clients.withClientDetails(jdbcClientDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("secret");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Bean
public PasswordEncoder userPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
public class DefaultAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName() == null || authentication.getCredentials() == null
|| authentication.getName().isEmpty() || authentication.getCredentials().toString().isEmpty()) {
return null;
}
final String userName = authentication.getName();
final String password = (String) authentication.getCredentials();
// final String userTable = how to get this?
// make db query in correct table based on value of userTable
User user = null;
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (UserAuthority authority : user.getUserAuthorities()) {
authorities.add(new CustomGrantedAuthority(authority.getAuthority().getName()));
}
Map<String, String> userDetails = new HashMap<>();
userDetails.put("username", userName);
return new UsernamePasswordAuthenticationToken(userDetails, password, authorities);
}
#Override
public boolean supports(Class<?> authentication) {
return false;
}
}
#Controller
public class OAuthController {
#RequestMapping("/login")
public String login() {
return "login";
}
}
I am expecting that my client app is redirected to the custom login page, once login button is pressed my custom AuthenticationProvider will lookup for user in the correct table based on the extra field in the custom login page.
For the first point you just have to configure your custom login-path for the login in your WebSecurityConfigurerAdapter.
Add .formLogin().loginPage("/login").permitAll()
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
Its not clear for me how to glue my CustomPasswordEncoder to the authentication process of spring boot. I define in a configuration that spring boot should use my CustomAuthenticationProvider with my UserDetailsService and my CustomPasswordEncoder
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.authenticationProvider(customAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new CustomPasswordEncoder();
return encoder;
}
}
My CustomPasswordEncoder will encode to a md5 value (I know its unsecure, but its a legacy database)
#Component
public class CustomPasswordEncoder implements PasswordEncoder{
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
}
In the CustomAuthtenticationProvider the authentication check will be done. The delivered password will be encoded by using the passwordEncoder.encode() The user will be fetched from the database, then I am using the passwordEncoder again do a match. If the match is successfull then the authentication object will be generated.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserServiceImpl userService;
#Autowired
private CustomPasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("authentication = [" + authentication + "]");
String name = authentication.getName();
Object credentials = authentication.getCredentials();
String password = credentials.toString();
//why is this necessary isnt it called automatically?
String passwordEncoded = passwordEncoder.encode((CharSequence) credentials);
Optional<UserEntity> userOptional = userService.findByUsername(name);
if (userOptional.isPresent() && passwordEncoder.matches(passwordEncoded, userOptional.get().getPassword())) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(userOptional.get().getRoles().toString()));
Authentication auth = new
UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
return auth;
}
else{
throw new BadCredentialsException("Authentication failed for " + name);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Is this the correct approach? I thought the CustomPasswordEncoder will be used "automatically" or ist that only the case if you use one of the provided authenticationProviders like jdbcAuthenticationProvider. Maybe someone can explain the order of events of the authentication process. I did some research in the net but still I cannot understand this in detail.
First as you can see from the matches method it validates the raw password (thus as entered by the user) with the encoded password. So the code for encoding belongs in the matches method instead of what you have now.
public class CustomPasswordEncoder implements PasswordEncoder{
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String rawEncoded = encode(rawPassword);
return Objects.equals(rawEncoded, encodedPassword);
}
}
Now you can remove the encoding line/step whatever from your code.
However you don't really need a custom AuthenticationProvider as that is generally only needed if you add another authentication mechanism like LDAP or OAuth.
What you need is an adapter for your UserService to a UserDetailsService and use that. I assume that the UserDetailsServiceImpl does exactly that. If not you can use something like the code below.
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService delegate;
public UserDetailsServiceAdapter(UserService delegate) {
this.delegate=delegate;
}
public UserDetails loadUserByUsername(String username) {
reutrn userService.findByUsername(name)
.map(this::toUserDetails).orElseThrow(() -> new UsernameNotFoundException("Unknown user " + username);
}
private UserDetails toUserDetails(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(r -> authorities.add(new SimpleGrantedAuthority(r));
return new UserDetails(user.getUsername(), user.getPassword(), authorities);
}
}
Now you can use your PasswordEncoder and this adapter in the configuration and you don't need your custom AuthenticationProvider.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new CustomPasswordEncoder();
return encoder;
}
}
I store information about users in seperated tables owners,employees,users i am trying to use java configuration in spring security.
I have created three diferent authentication providers for each user type, but only the Users Provider is being triggered. I have read the spring security docs and the only way to do this seems to be is create class with multiple embedded classes extended from WebSecurityConfigurerAdapter but i don't want to do it this way because it requires a lot of duplicating code, is there any other way
I tryed to use the simple userDetailService inside which i send request to all tables in databese but still there is not results, only one query is buing executed and nothing, the only responce i get is:
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
.s.a.DefaultAuthenticationEventPublisher : No event was found for the
exception
org.springframework.security.authentication.InternalAuthenticationServiceException
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for
failed:
org.springframework.security.authentication.InternalAuthenticationServiceException:
No entity found for query; nested exception is
javax.persistence.NoResultException: No entity found for query
But i never throw any exception!! And the most strange is that i can see in the debugger how the execution rapidly stops right after em.createQuery(..).getSingleResult().. and that's it, nothing more! There is no return statement no exception nothing, wtf!!
This is part of my current configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(createAuthenticationProvider(employeeDetailService()))
.authenticationProvider(createAuthenticationProvider(ownerDetailsService()))
.authenticationProvider(createAuthenticationProvider(userDetailsService()));
}
#Bean
public OwnerDetailsService ownerDetailsService() {
return new OwnerDetailsService();
}
#Bean
public EmployeeDetailServiceImpl employeeDetailService() {
return new EmployeeDetailServiceImpl();
}
#Bean
public UserDetailsServiceImpl userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(6);
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new MySimpleUrlAuthenticationSuccessHendler();
}
private AuthenticationProvider createAuthenticationProvider(UserDetailsService service) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(service);
provider.setPasswordEncoder(passwordEncoder());
provider.setHideUserNotFoundExceptions(true);
return provider;
}
User detail services:
#Service
public abstract class CustomUserDetailService implements UserDetailsService{
#Autowired
IDBBean dao;
protected CustomUserDetails getUser(GetUserByNameFunction function, String name) {
return createUser(function.get(name));
}
protected CustomUserDetails createUser(Authenticational user) {
return new CustomUserDetails(user, getAuthorities(user.getAuthority()));
}
protected List<GrantedAuthority> getAuthorities(String authority) {
return Collections.singletonList(new SimpleGrantedAuthority(authority));
}
}
Implementations
public class EmployeeDetailServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getEmployeeByEmail, email);
}
}
public class OwnerDetailsService extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getOwnerByEmail, email);
}
}
public class UserDetailsServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return super.getUser(dao::getUserByEmail, userName);
}
}
Custom user details:
private Long id;
private String userEmail;
public CustomUserDetails(Authenticational user,
Collection<? extends GrantedAuthority> authorities) {
super(
user.getName(),
user.getPassword().toLowerCase(),
user.isEnabled(),
true,
true,
true,
authorities);
upadateValues(user);
}
private void upadateValues(Authenticational user) {
this.id = user.getId();
this.userEmail = user.getEmail();
}
Just to clarify something from the other answer:
Your authentication providers are stored in a list inside ProviderManager that iterates your authentication request through them. If your authentication provider throws AuthenticationException (BadCredentialsException extends AuthenticationException), then the ProviderManager will try another provider. If you set the hideUserNotFoundExceptions property, then it will also wrap and ignore UsernameNotFoundException and try another provider in this case too.
If I were you I would start by placing a debugging point inside ProviderManager's authenticate method. From there you can find out why the other authentication providers are not being called for their authenticate method.
Also I would think about having only one authentication provider with one UserDetailsService. It seems to me that you are doing a lot of complex not really needed operations like passing function to your abstract implementation when all you could do would be to have one UserDetailsService that would ask all your DAOs for a user. Which is basically what you're trying to accomplish but minus 2 authentication providers, minus 1 abstract class and minus 2 UserDetailsService implementations.
Spring Security will not try other authentication providers if a provider throws an AccountStatusException or if a UserDetailsService throws a UserNameNotFoundException or any other AuthenticationException
If you want other providers to be tried, then the loadUserByUserName methods of your UserDetailsServiceImpl and OwnerDetailsService should not throw the UserNameNotFound exception.
You should decide if you either want to return a dummy anonymous UserDetails object that will be used exclusively for fallback or some other mechanism to not throw the exception when a user is not available in your UserDetailsService implementation
this is an example I'v done to provide multi-auth for my application which have two diffirent user : admin and client .
ps: the admin and the client are two diffirent model.
public class CustomUserDetails implements UserDetails{
private Admin admin;
private Client client;
public CustomUserDetails(Admin admin) {
this.admin = admin;
}
public CustomUserDetails(Client client) {
this.client = client;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
if((admin != null)&&(client==null)) {
return admin.getPassword();
}
else {
return client.getPassword();
}
}
#Override
public String getUsername() {
if((admin != null)&&(client==null)) {
return admin.getEmail();
}
else {
return client.getMail();
}
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
the CustomUserDetailsService class :
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
AdminRepository adminRepo;
#Autowired
ClientRepository clientRepo;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Admin admin = adminRepo.findByEmail(email);
Client client = clientRepo.findByEmail(email);
if((admin == null)&&(client==null)) {
throw new UsernameNotFoundException("User not found");
}
else if((admin != null)&&(client==null)) {
return new CustomUserDetails(admin);
}
else {
return new CustomUserDetails(client);
}
}
}
I have Spring Security working within my application to authenticate a user with one password. I'm trying to meet a requirement that an override password will also authenticate that same user.
How can I do this with Spring Security?
It is possible, you will have to implement your own AuthenticationProvider possibly by extending the existing DaoAuthenticationProvider (see additionalAuthenticationChecks() in there).
Also the user is only associated with a single password by default (UserDetails.getPassword()), so you will need to have an extension of that class holding multiple passwords, and a corresponding implementation of UserDetailsService that knows how to load the user along with its passwords.
It is easy to do by providing multiple 'AuthenticationProvider' with 'UserDetailsService'.
private DaoAuthenticationProvider userAuthProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userDetailsService);
return provider;
}
private DaoAuthenticationProvider superVisorAuthProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(supervisorDetailService);
return provider;
}
then
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userAuthProvider());
auth.authenticationProvider(superVisorAuthProvider());
}
As has been mentioned - you can overwrite 'additionalAuthenticationChecks'
Hope this helps somebody.
#Slf4j
#Service
class FlexibleAuthenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider {
#Autowired
UserDetailsService userDetailsService
#Autowired
PasswordEncoder passwordEncoder
#PostConstruct
def init() {
super.setPasswordEncoder(passwordEncoder)
super.setUserDetailsService(userDetailsService)
}
#Override
protected void additionalAuthenticationChecks(
UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
try {
super.additionalAuthenticationChecks(userDetails, authentication)
} catch (AuthenticationException e) {
log.error('Unable to authenticate with regular credentials')
try {
def mutableUserDetails = new MutableUser(userDetails)
mutableUserDetails.password = 'alternatepassword'
return super.additionalAuthenticationChecks(mutableUserDetails, authentication)
} catch (AuthenticationException err) {
log.error('Token based authentication failed')
}
throw e
}
}
static class MutableUser implements UserDetails {
private String password
private final UserDetails delegate
MutableUser(UserDetails user) {
this.delegate = user
this.password = user.password
}
String getPassword() {
return password
}
void setPassword(String password) {
this.password = password
}
....
}
}
#Configuration
class AuthWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
FlexibleAuthenticationProvider flexibleAuthenticationProvider
....
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(flexibleAuthenticationProvider)
}
....
}