I am using Spring Security with SpringMVC to create a web application (I will refer to this as the WebApp for clarity) that speaks to an existing application (I will refer to this as BackendApp).
I want to delegate authentication responsibilities to the BackendApp (so that I don't need to synchronise the two applications).
To implement this, I would like the WebApp (running spring security) to communicate to the BackendApp via REST with the username and password provided by the user in a form and authenticate based on whether the BackendApp's response is 200 OK or 401 Unauthorised.
I understand I will need to write a custom Authentication Manager to do this however I am very new to spring and can't find any information on how to implement it.
I believe I will need to do something like this:
public class CustomAuthenticationManager implements AuthenticationManager{
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String pw = authentication.getCredentials().toString();
// Code to make rest call here and check for OK or Unauthorised.
// What do I return?
}
}
Do I set authentication.setAuthenticated(true) if successful and false if otherwise and thats it?
Once this is written, how do I configure spring security to use this authentication manager using a java configuration file?
Thanks in advance for any assistance.
Authentication. An AuthenticationManager can do one of 3 things in its authenticate() method: Return an Authentication (normally with authenticated=true ) if it can verify that the input represents a valid principal. Throw an AuthenticationException if it believes that the input represents an invalid principal.
Simply put, Spring Security hold the principal information of each authenticated user in a ThreadLocal – represented as an Authentication object. In order to construct and set this Authentication object – we need to use the same approach Spring Security typically uses to build the object on a standard authentication.
The Authentication Manager is only a interface and actual implementation of the authenticate method is provided by the ProviderManager. The ProviderManager has a list of AuthenticationProviders. From it's authenticate method it calls the authenticate method of the appropriate AuthenticateProvider.
Take a look at my sample below. You have to return an UsernamePasswordAuthenticationToken. It contains the principal and the GrantedAuthorities. Hope I could help :)
public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getPrincipal() + ""; String password = authentication.getCredentials() + ""; User user = userRepo.findOne(username); if (user == null) { throw new BadCredentialsException("1000"); } if (!encoder.matches(password, user.getPassword())) { throw new BadCredentialsException("1000"); } if (user.isDisabled()) { throw new DisabledException("1001"); } List<Right> userRights = rightRepo.getUserRights(username); return new UsernamePasswordAuthenticationToken(username, null, userRights.stream().map(x -> new SimpleGrantedAuthority(x.getName())).collect(Collectors.toList())); }
PS: userRepo and rightRepo are Spring-Data-JPA Repositories which access my custom User-DB
SpringSecurity JavaConfig:
@Configuration @EnableWebMvcSecurity public class MySecurityConfiguration extends WebSecurityConfigurerAdapter { public MySecurityConfiguration() { super(false); } @Override protected AuthenticationManager authenticationManager() throws Exception { return new ProviderManager(Arrays.asList((AuthenticationProvider) new AuthProvider())); } }
In its most simplest:
@Override public Authentication authenticate(Authentication auth) throws AuthenticationException { String username = auth.getName(); String password = auth.getCredentials().toString(); // to add more logic List<GrantedAuthority> grantedAuths = new ArrayList<>(); grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER")); return new UsernamePasswordAuthenticationToken(username, password, grantedAuths); }
First you must configure Spring security to use your custom AuthenticationProvider. So, in your spring-security.xml (or equivalent config file) you must define wich class is implementing this feature. For example:
<authentication-manager alias="authenticationManager">
<authentication-provider ref="myAuthenticationProvider" />
</authentication-manager>
<!-- Bean implementing AuthenticationProvider of Spring Security -->
<beans:bean id="myAuthenticationProvider" class="com.teimas.MyAutenticationProvider">
</beans:bean>
Secondly you must implement AuthenticationProvider as in your example. Specially the method authenticate(Authentication authentication) in which your rest call must be. For example:
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
User user = null;
try {
//use a rest service to find the user.
//Spring security provides user login name in authentication.getPrincipal()
user = userRestService.loadUserByUsername(authentication.getPrincipal().toString());
} catch (Exception e) {
log.error("Error loading user, not found: " + e.getMessage(), e);
}
if (user == null) {
throw new UsernameNotFoundException(String.format("Invalid credentials", authentication.getPrincipal()));
} else if (!user.isEnabled()) {
throw new UsernameNotFoundException(String.format("Not found enabled user for username ", user.getUsername()));
}
//check user password stored in authentication.getCredentials() against stored password hash
if (StringUtils.isBlank(authentication.getCredentials().toString())
|| !passwordEncoder.isPasswordValid(user.getPasswordHash(), authentication.getCredentials().toString()) {
throw new BadCredentialsException("Invalid credentials");
}
//doLogin makes whatever is necesary when login is made (put info in session, load other data etc..)
return doLogin(user);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With