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.

nbdy_
  • 729
  • 1
  • 9
  • 18
  • 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. – We are Borg Aug 05 '15 at 07:49
  • Also, please mention your application with some relevant names, this and that application is confusing. – We are Borg Aug 05 '15 at 07:50
  • 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. – Stefan Aug 05 '15 at 07:55
  • @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. – nbdy_ Aug 05 '15 at 08:01
  • 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. – We are Borg Aug 05 '15 at 08:03
  • see this link : http://stackoverflow.com/questions/22606751/custom-authentication-provider-with-spring-security-and-java-config/42245443 – M2E67 Feb 15 '17 at 10:09

5 Answers5

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

}
Halko Karr-Sajtarevic
  • 2,248
  • 1
  • 16
  • 14
  • 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? – nbdy_ Aug 05 '15 at 08:02
  • i've edited my answer to match your comment (see SpringSecurity JavaConfig) – Halko Karr-Sajtarevic Aug 05 '15 at 08:17
  • 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 :S – nbdy_ Aug 05 '15 at 08:49
  • 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 `AuthenticationProvider`s. -> You can check the username / password using `REST` in your `authenticate` method. – Halko Karr-Sajtarevic Aug 05 '15 at 11:00
  • There is a cast error. (AuthenticationProvider) new AuthProvider(). You meant to cast AuthenticationManager to AuthenticationProvider? – Dai Kaixian Jan 12 '17 at 11:55
  • @HalkoSajtarevic What is the type of AuthProvider? – Dai Kaixian Jan 12 '17 at 11:56
  • AuthProvider implements AuthenticationProvider and contains the authenticate method mentioned above – Halko Karr-Sajtarevic Jan 12 '17 at 18:05
  • 1
    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. – Impulse The Fox Jan 04 '20 at 15:33
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);
    }
mel3kings
  • 8,857
  • 3
  • 60
  • 68
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.

Andrew Gans
  • 700
  • 6
  • 9
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);
} 
Georgios Syngouroglou
  • 18,813
  • 9
  • 90
  • 92
Ricardo Vila
  • 1,626
  • 1
  • 18
  • 34
  • 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? – nbdy_ Aug 05 '15 at 10:35
  • I was talking about AuthenticationProvider that is another Interface almost the same than authenticationManager. You can use implement AuthenticationManager if you prefer. – Ricardo Vila Aug 05 '15 at 13:03
  • 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 :S – nbdy_ Aug 05 '15 at 14:02
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();
    }
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70