33

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.

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. Commented Aug 5, 2015 at 7:49
  • Also, please mention your application with some relevant names, this and that application is confusing. Commented Aug 5, 2015 at 7: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. Commented Aug 5, 2015 at 7: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. Commented Aug 5, 2015 at 8: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. Commented Aug 5, 2015 at 8:03

5 Answers 5

45

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

}
Sign up to request clarification or add additional context in comments.

8 Comments

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?
i've edited my answer to match your comment (see SpringSecurity JavaConfig)
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
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.
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.
|
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);
    }

Comments

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.

Comments

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

3 Comments

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?
I was talking about AuthenticationProvider that is another Interface almost the same than authenticationManager. You can use implement AuthenticationManager if you prefer.
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
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();
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.