Custom Authentication Manager with Spring Security and Java Configuration
Asked Answered
E

5

32

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.

Eccrinology answered 5/8, 2015 at 7:38 Comment(6)
So, you basically want to authenticate via REST, is that correct? Also, you will not only get the response type as 200 or 401, you will SessionID also from Spring-Security, which you can directly use via REST to access secured resources.Berglund
Also, please mention your application with some relevant names, this and that application is confusing.Berglund
You should have a deep look into the Spring Sec. Reference, you need to set up a REST Client, secure the service, make a secure connection, dont store password in strings, keep in mind, that if the other app is down this will be down too etc... Better set up an LDAP server or just share the database (readonly) with the other app. You should ask a consultant.Wickham
@WeareBorg Good point, I edited the question to hopefully make it clearer. I don't understand your first comment though. Stefan thanks for your advice.Eccrinology
Yes, that's what I said, your webapp does not need to write any authentication code. If you are using Spring-Security in one of the webapps, you can always call the j_spring_security_check and get the response. Depending upon response, you can either allow access to resources or not. No delegation, nothing required.Berglund
see this link : #22607251Settler
B
44

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()));
}

}
Benham answered 5/8, 2015 at 7:54 Comment(8)
Thanks thats very helpful. So the authenticate method returns this UsernamePasswordAuthenticationToken only if successful? Also, how did you configure spring security to use your custom authentication manager?Eccrinology
i've edited my answer to match your comment (see SpringSecurity JavaConfig)Benham
Thanks for your help but I don't think this is what I'm after. To my understanding, the AuthenticationProvider (which is what you showed) passes that token to an AuthenticationManager, which then goes on to compare the principal and credentials to what the user provided. I want to implement my own AuthenticationManager and tell it NOT to compare username and password with an AuthenticationProvider, but rather to speak to another server via REST to determine whether the information is correct. Sorry about the confusion... My question probably wasn't clear :SEccrinology
I've updated my answer. The way the JavaConfig is working now should be OK for you. ProviderManager tries to authenticate using the given list of AuthenticationProviders. -> You can check the username / password using REST in your authenticate method.Benham
There is a cast error. (AuthenticationProvider) new AuthProvider(). You meant to cast AuthenticationManager to AuthenticationProvider?Fob
@HalkoSajtarevic What is the type of AuthProvider?Fob
AuthProvider implements AuthenticationProvider and contains the authenticate method mentioned aboveBenham
For security reasons, I'd recommend setting the password to null, else it will be saved and visible in your authenticated Principal. Also, the user enabled/disabled check should be after the password check to not expose disabled users without knowing their password.Gar
C
8

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);
    }
Culinarian answered 20/5, 2016 at 2:29 Comment(0)
S
4

My solution is almost the same as the first answer:

1) You need a class which implements the Authentication Provider

@Service
@Configurable
public class CustomAuthenticationProvider implements AuthenticationProvider    {
      @Override
      public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    // Your code of custom Authentication
}
}

2) Opposite to the first answer you don't need to have following code in your WebSecurityConfiguration if you have only this custom provider.

@Override
protected AuthenticationManager authenticationManager() throws Exception {
     return new ProviderManager(Arrays.asList((AuthenticationProvider) new  AuthProvider()));
}

The issue is that Spring looks for available providers and use the default if nothing else is found. But as you have the implementation of the AuthenticationProvider - your implementation will be used.

Scupper answered 17/11, 2016 at 6:13 Comment(0)
A
3

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);
} 
Aitch answered 5/8, 2015 at 8:9 Comment(3)
Thanks for the advice, I'm assuming you meant AuthenticationManager not AuthenticationProvider?? I don't think I need an AuthenticationProvider because I don't want to compare any credentials... Do you happen to know the java version of that config for the authentication manager?Eccrinology
I was talking about AuthenticationProvider that is another Interface almost the same than authenticationManager. You can use implement AuthenticationManager if you prefer.Aitch
Just to clarify... I initially thought authentication provider was used by the authentication manager to retrieve user details... It seems I misunderstood how spring security was working. From what I understand now, the authentication provider can be solely responsible for authenticating and setting the security context. Correct me if I'm wrong :SEccrinology
P
0

This is how I did using component-based configuration (SecurityFilterChain) and new authorizeHttpRequests

@Bean
    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
            .authorizeHttpRequests(auth -> auth
            .antMatchers(UNPROTECTED_URLS).permitAll()
            .oauth2ResourceServer()
            .accessDeniedHandler(restAccessDeniedHandler)
            .authenticationEntryPoint(authenticationEntryPoint)
            .jwt()
            .authenticationManager(new ProviderManager(authenticationProvider)); // this is custom authenticationProvider
        return httpSecurity.build();
    }
Pleat answered 24/9, 2022 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.