Thursday, January 12, 2012

Forgot Password with LDAP, CAS and Spring: Credentials expired

I have already documented the steps I followed to integrate existing Spring applications that happen to be using LDAP with CAS including how to support "Forgot Password" functionality.

Some CAS client applications are currently using certain complicated logic to handle Terms and Conditions, Forgot Password and other features through custom filters and they were failing with a 401 ERROR "User credentials have expired". Basically the user hits the Forgot Password link in CAS which shows a CAS Client page where the email is given and after submission the temporary password is mailed. Any attempt to move to any page from that moment on results in a 401.

The problem here is that the default implementation when using Spring CAS client is to test the forcePasswordChange LDAP attribute and it currently throws an Exception rather than easily allowing to hook into the current implementation let us say with a property like "IGNORE_CREDENTIALS_EXPIRATION". As the project is already handling password expiration it does not need that extra check from Spring CAS Client so after some research I had to hack into Spring code.

I found that a possible solution is to use a CustomCasAuthenticationProvider to override CasAuthenticationProvider#authenticateNow() however I had to copy the whole code from the latter because of scope problems. Basically the original class is not designed to be inherited (at least for this feature). Below is the only method I changed in the class:
    private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
        try {
            final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), serviceProperties.getService());
            final UserDetails userDetails = loadUserByAssertion(assertion);
            try {
            } catch (CredentialsExpiredException e) {
                if (log.isDebugEnabled()) {
                    log.debug("Credentials expired but we handle that case outside of the CAS logic.");
            return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), userDetails.getAuthorities(), userDetails, assertion);
        } catch (final TicketValidationException e) {
            throw new BadCredentialsException(e.getMessage(), e);


Ganesh Prasad said...

> I had to copy the whole code from the latter because of scope problems.

Can't you use a Maven overlay project? I think all modifications to CAS are done this way now. See this example:

Nestor Urquiza said...

@Ganesh, probably yes. Not sure At the time I posted this the above worked for me. Using overlay or not the issue I experimented with the class would not disappear unless the method scope is changed. Not sure what is the status of that code in current versions though. At the moment I am not working with CAS.