Crowd and Spring Security 4 integration with SSO

Andrus Suitsu September 10, 2016

Using another Crowd-Spring integration topic I have managed to get Crowd and Spring Security 4 integration working. But I fail to get SSO working. It seems that the application doesn't receive the authentication token from Crowd.

The code I use is this:

@Configuration
@EnableWebSecurity(debug = false)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    private CrowdSSOAuthenticationProcessingFilter filter;

    @Override
    protected void configure( HttpSecurity http ) throws Exception
    {
        http
                .authenticationProvider( crowdAuthenticationProvider() )
                .addFilter( crowdSSOAuthenticationProcessingFilter() )
                .authorizeRequests()
                .antMatchers( "/", "/home" ).permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage( "/login" )
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }

    @Override
    protected void configure( AuthenticationManagerBuilder auth ) throws Exception
    {
        auth.authenticationProvider( crowdAuthenticationProvider() );
    }

    @Bean
    public CrowdSSOAuthenticationProcessingFilter crowdSSOAuthenticationProcessingFilter() throws Exception
    {
        filter = new CrowdSSOAuthenticationProcessingFilter();
        filter.setHttpAuthenticator( httpAuthenticator() );
        filter.setAuthenticationManager( authenticationManager() );
        filter.setAuthenticationFailureHandler( authenticationFailureHandler() );
        filter.setAuthenticationSuccessHandler( authenticationSuccessHandler() );
                filter.setFilterProcessesUrl( "/j_security_check" );
                filter.setUsernameParameter( "j_username" );
                filter.setPasswordParameter( "j_password" );
        return filter;
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler()
    {
        UsernameStoringAuthenticationFailureHandler failureHandler = new UsernameStoringAuthenticationFailureHandler();
        failureHandler.setDefaultFailureUrl( "/login?error=true" );
        return failureHandler;
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler()
    {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setDefaultTargetUrl( "/home" );
        return successHandler;
    }

    @Bean
    CrowdAuthenticationProvider crowdAuthenticationProvider() throws IOException
    {
        return new RemoteCrowdAuthenticationProvider( crowdAuthenticationManager(), httpAuthenticator(), crowdUserDetailsService() );
    }

    @Bean
    public CrowdUserDetailsService crowdUserDetailsService() throws IOException
    {
        CrowdUserDetailsServiceImpl crowdUserDetailsService = new CrowdUserDetailsServiceImpl();
        crowdUserDetailsService.setUserManager( userManager() );
        crowdUserDetailsService.setAuthorityPrefix( "" );
        crowdUserDetailsService.setGroupMembershipManager( new CachingGroupMembershipManager( securityServerClient(), userManager(), groupManager(), cache() ) );
        return crowdUserDetailsService;
    }

    @Bean()
    public HttpAuthenticator httpAuthenticator() throws IOException
    {
        return new HttpAuthenticatorImpl( crowdAuthenticationManager() );
    }

    @Bean
    public AuthenticationManager crowdAuthenticationManager() throws IOException
    {
        return new SimpleAuthenticationManager( securityServerClient() );
    }

    @Bean
    public GroupManager groupManager() throws IOException
    {
        return new CachingGroupManager( securityServerClient(), cache() );
    }

    @Bean
    public CachingUserManager userManager() throws IOException
    {
        return new CachingUserManager( securityServerClient(), cache() );
    }

    @Bean
    public SecurityServerClient securityServerClient() throws IOException
    {
        return new SecurityServerClientImpl( soapClientProperties() );
    }

    @Bean
    public SoapClientProperties soapClientProperties() throws IOException
    {
        Properties prop = new Properties();
        try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream( "crowd.properties" ))
        {
            prop.load( in );
        }
        return SoapClientPropertiesImpl.newInstanceFromProperties( prop );
    }

    @Bean
    public BasicCache cache()
    {
        return new CacheImpl( Thread.currentThread().getContextClassLoader().getResource( "crowd-ehcache.xml" ) );
    }
}

 

Login form is this:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/j_security_check}" method="post">
            <div><label> User Name : <input type="text" name="j_username"/> </label></div>
            <div><label> Password: <input type="password" name="j_password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

 

Authentication succeeds

12:02:59.193 [http-nio-8080-exec-3] DEBUG c.a.c.i.s.CrowdSSOAuthenticationProcessingFilter - Authentication success. Updating SecurityContextHolder to contain: com.atlassian.crowd.integration.springsecurity.CrowdSSOAuthenticationToken@35684235...

But going through the filter chain

CrowdHttpTokenHelperImpl - Unable to find a valid Crowd token.

and later AnonymousAuthenticationFilter populates the SecurityContextHolder with anonymous token.

 

I had it all working with Spring Security 3, the SSO stuff too. The crowd.properies file and ehcache.xml files are exactly the same. But with Spring 4 I have no clue where to search for the problem. My last efforts used Crowd libraries 2.7.1, but I had no better luck with 2.8.3 or 2.9.1.

Can someone point me out what is wrong with my code.

2 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

1 vote
Answer accepted
Andrus Suitsu September 21, 2016

My initial solution appears to work as expected – SSO cookie is created.

public class MySSOAuthenticationProcessingFilter extends CrowdSSOAuthenticationProcessingFilter
{
    private static final Logger logger = LoggerFactory.getLogger( MySSOAuthenticationProcessingFilter.class );

    private HttpAuthenticator httpAuthenticator;

    @Override
    protected void successfulAuthentication( HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult )
            throws IOException, ServletException
    {
        storeTokenIfCrowd( request, response, authResult );
        super.successfulAuthentication( request, response, chain, authResult );
    }

    public void setHttpAuthenticator( HttpAuthenticator httpAuthenticator )
    {
        this.httpAuthenticator = httpAuthenticator;
        super.setHttpAuthenticator( httpAuthenticator );
    }

    private void storeTokenIfCrowd( HttpServletRequest request, HttpServletResponse response, Authentication authResult )
    {
        if ( authResult instanceof CrowdSSOAuthenticationToken && authResult.getCredentials() != null )
        {
            try
            {
                httpAuthenticator.setPrincipalToken( request, response, authResult.getCredentials().toString() );
            }
            catch ( Exception var5 )
            {
                logger.error( "Unable to set Crowd SSO token", var5 );
            }
        }
    }
}
0 votes
Andrus Suitsu September 21, 2016

Looks like the root cause of my problem is that CrowdSSOAuthenticationProcessingFilter has an overridden method

successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult)

Spring Security 3 had it this way but Spring Security4 expects the method to additionally contain FilterChain as a third parameter and therefore the method in CrowdSSOAuthenticationProcessingFilter is never called and the token is never saved into a cookie.

The solution is to define my own filter class and override the relevant methods.

Ludovic MAÎTRE May 4, 2018

Thanks!

TAGS
AUG Leaders

Atlassian Community Events