5

I have created a custom AuthenticationProvider to perform custom security checks. I have also created custom exceptions that inherit from AccountStatusException to notify user status problems such as when the user has not verified his account for an specific period of time.My UserDetails is also acustom implementation.

Here is the code for the security checks I perform. Code that is irrelevant to the case has been omitted.

public class SsoAuthenticationProvider implements AuthenticationProvider {

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (String) authentication.getPrincipal();
        User user = null;
        if (username != null) {
            user = getUserRepository().findByUserName(username);
            if (user != null) {
                if (user.getEnabled() != 0) {
                    if ((user.getUserDetail().getConfirmed() != 0)
                            || ((new Date().getTime() - user.getUserDetail().getRequestDate().getTime()) / (1000 * 60 * 60 * 24)) <= getUnconfirmedDays()) {
                        if (getPasswordEncoder().isPasswordValid(user.getPassword(),
                                (String) authentication.getCredentials(), user)) {
                            user.authenticated = true;
                            user.getAuthorities();
                        }
                    } else {
                        throw new UserNotConfirmedAndTimeExceeded(
                                "User has not been cofirmed in the established time period");
                    }
                } else {
                    throw new DisabledException("User is disabled");
                }
            } else {
                throw new BadCredentialsException("User or password incorrect");
            }
        } else {
            throw new AuthenticationCredentialsNotFoundException("No credentials found in context");
        }
        return user;
    }
}

The SsoAuthenticationProvider checks:

  1. That the username is registered (exists in the db)
  2. That the user has confirmed his email
  3. If the user has not confirmed his email, check that he is still in the grace period (this is a few days we give users to confirm their email while letting them access the site)
  4. If the user has not confirmed email and he is not in the grace period, throw security exception to signal these status and reject authentication

The problem is that not all of these exceptions are thrown up the stack up to the controller so it seems impossible to inform the user about the login problem.

Using UserDetails methods such as isEnabled() (and similar) is not a possibility as the semantics of our different user account statuses are completely different.

Is this the right approach to build custom security with custom exceptions? Should i implement sth else to make this work?

Mike Partridge
  • 5,128
  • 8
  • 35
  • 47
Daniel Cerecedo
  • 6,071
  • 4
  • 38
  • 51

2 Answers2

6

To close the previously asked question let me explain what we did. As I commented to previous responses, using provided methods in UserDetails objectis not feasible as you cannot capture all the login failure semantics with the given methods. In our case these semantics are still very limited but in other cases it could indfinitely extend over time to express different user situations. The exception approach was finally the best one. The final code looks like this

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username=(String)authentication.getPrincipal();
    User user=null;
    if(username!=null){
        user=getUserRepository().findByUserName(username);
        if(user!=null){
            if(user.getEnabled()!=0){
                if((user.getUserDetail().getConfirmed()!=0)||((new Date().getTime()-user.getUserDetail().getRequestDate().getTime())/(1000 * 60 * 60 * 24))<=getUnconfirmedDays()){
                    if(getPasswordEncoder().isPasswordValid(user.getPassword(), (String)authentication.getCredentials(), user)){
                        user.authenticated=true;
                        user.getAuthorities();
                    } else {
                        throw new BadCredentialsException("Password incorrect");
                    }
                }else{
                    throw new UserNotConfirmedAndTimeExceeded("User has not been cofirmed in the established time period");         
                }
            }else{
                throw new DisabledException("User is disabled");
            }
        }else{
            throw new BadCredentialsException("User does not exist");
        }
    }else{
        throw new AuthenticationCredentialsNotFoundException("No credentials found in context");
    }
    return user;
}

All exceptions are part of the spring security exception stack. This is, those custom exceptions inherit from some existing exception. Then, in your security controller you should check for security exceptions and treat them as desired. For example redirecting to different pages.

Hope this helps!

Daniel Cerecedo
  • 6,071
  • 4
  • 38
  • 51
2

i think its better to use other method/properties of user detail object for this purpose. like

isAccountNonExpired() 
isAccountNonLocked() 
isEnabled() 

and if you want to display custom error message then use message properties as explained in this article

Jigar Parekh
  • 6,163
  • 7
  • 44
  • 64
  • I think your suggestions are not applicable as the account status we try to communicate has completely different semantics. The account is not expired, is not locked, it is enabled but we no longer let the user log in until he confirms his email. – Daniel Cerecedo Aug 23 '12 at 07:49
  • may be you can use authentication-success-handler-ref to do this work – Jigar Parekh Aug 23 '12 at 10:25