Crowd Integration with Spring boot + Spring security using Java Beans

william daniels January 11, 2016

We use a number of spring boot projects built on top of spring security for providing small web apps for a number of purposes. We're strongly considering moving to using Crowd as a central authentication provider, but I'm having a lot of trouble getting spring boot configured to use java-style beans rather than the provided XML configuration, since that is no longer a recommended configuration (see http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-configuration-classes for verification that java-bean style configuration is the preferred way). 

 

My basic test-app for the security has a security configuration set up as follows: 

package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}

 

I'm really stumped as to how to modify this to accomodate the Crowd built-in springsecurity integration. (specifically the com.atlassian.crowd:crowd-integration-springsecurity:2.8.3). the XML I was attempting to use to try and bootstrap the functionality through a standard configuration annotated class was as follows: 

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
                        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<!--
    <debug />
    <beans:alias name="springSecurityFilterChain" alias="org.springframework.security.filterChainProxy"/>    
-->

    <!-- Added for Integrating Crowd with Spring Security -->
    
    <!-- 3.1 Configuring Centralised User Management -->
    
    <!-- 3.1.1 -->
    <beans:bean id="crowdUserDetailsService" class="com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsServiceImpl">
        <beans:property name="groupMembershipManager" ref="crowdGroupMembershipManager"/>
        <beans:property name="userManager" ref="crowdUserManager"/>
        <beans:property name="authorityPrefix" value="ROLE_"/>

<!--
        <beans:property name="groupToAuthorityMappings">
            <beans:bean factory-bean="groupToAuthorityMappings" factory-method="entrySet" />
        </beans:property>
-->
    </beans:bean>

<!--
    <util:map id="groupToAuthorityMappings">
        <beans:entry key="crowd-administrators" value="ROLE_crowd-administrators" />
        <beans:entry key="some-other-group" value="specific-authority-for-other-group" />
    </util:map>
-->

    <!-- 3.1.2 -->
    <beans:bean id="crowdAuthenticationProvider" class="com.atlassian.crowd.integration.springsecurity.RemoteCrowdAuthenticationProvider">
        <beans:constructor-arg ref="crowdAuthenticationManager"/>
        <beans:constructor-arg ref="httpAuthenticator"/>
        <beans:constructor-arg ref="crowdUserDetailsService"/>
    </beans:bean>
    
    <!-- 3.2 -->

    <http pattern="/console/static/session-context"
          entry-point-ref="crowdAuthenticationProcessingFilterEntryPoint">
    </http>

    <http pattern='/console/static/**' security='none'/>

    <http auto-config="false"
          entry-point-ref="crowdAuthenticationProcessingFilterEntryPoint"
          access-denied-page="/denied.html">
        <custom-filter position="FORM_LOGIN_FILTER" ref='authenticationProcessingFilter'/>
        <custom-filter position="LOGOUT_FILTER" ref='logoutFilter'/>
        
        <intercept-url pattern="/console/secure/**" access="ROLE_crowd-administrators"/>
        <intercept-url pattern="/console/user/**" access="IS_AUTHENTICATED_FULLY"/>
        
        <intercept-url pattern="/console/resource-with-own-check/**" access='IS_AUTHENTICATED_ANONYMOUSLY'/>
    </http>
    

    <authentication-manager alias='authenticationManager'>
        <authentication-provider ref='crowdAuthenticationProvider'/>
    </authentication-manager> 

    <beans:bean id="crowdAuthenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login.html"/>
    </beans:bean>
 
    <beans:bean id="authenticationProcessingFilter" class="com.atlassian.crowd.integration.springsecurity.CrowdSSOAuthenticationProcessingFilter">
        <beans:property name="httpAuthenticator" ref="httpAuthenticator"/>
        <beans:property name="authenticationManager" ref="authenticationManager"/>
        <beans:property name="filterProcessesUrl" value="/j_security_check"/>
        <beans:property name="authenticationFailureHandler">
            <beans:bean class="com.atlassian.crowd.integration.springsecurity.UsernameStoringAuthenticationFailureHandler">
                <beans:property name="defaultFailureUrl" value="/console/login.action?error=true"/>
            </beans:bean>
        </beans:property>
         
        <beans:property name="authenticationSuccessHandler">
            <beans:bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
                <beans:property name="defaultTargetUrl" value="/console/defaultstartpage.action"/>
            </beans:bean>
        </beans:property>
    </beans:bean>
    
     <beans:bean id="crowdLogoutHandler" class="com.atlassian.crowd.integration.springsecurity.CrowdLogoutHandler">
        <beans:property name="httpAuthenticator" ref="httpAuthenticator"/>
    </beans:bean>
 
     <beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <beans:constructor-arg value="/login.html"/>
        <beans:constructor-arg>
            <beans:list>
                <beans:ref bean="crowdLogoutHandler"/>
                <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            </beans:list>
        </beans:constructor-arg>
        <beans:property name="filterProcessesUrl" value="/console/logoff.action"/>
    </beans:bean>

 

The above is the most up-to-date comprehensive example of an XML security file I can find anywhere, and it is incompatible with spring security 4.0 (if you increment the version, you get xml violations all over the place). I can't tell where/how the httpAuthenticator is getting resolved out, as I don't see any beans that have that ID, which makes it very troublesome. 

 

Any help would be very much appreciated, since if this gets resolved it would result in an immediate sale. 

Regards, 

 

-William 

 

Extra info: 

running spring boot version 1.3.1-Release, spring security version 4.0

 

3 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
william daniels January 12, 2016

So, I think I've actually figured this out, it at least gives me a jumping-off point to further customize the spring boot integration. the primary piece of magic is the WebSecurityConfig class, which now looks like the following: 

package hello;
import com.atlassian.crowd.integration.http.HttpAuthenticator;
import com.atlassian.crowd.integration.http.HttpAuthenticatorImpl;
import com.atlassian.crowd.integration.springsecurity.RemoteCrowdAuthenticationProvider;
import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsService;
import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsServiceImpl;
import com.atlassian.crowd.service.AuthenticationManager;
import com.atlassian.crowd.service.GroupManager;
import com.atlassian.crowd.service.UserManager;
import com.atlassian.crowd.service.cache.BasicCache;
import com.atlassian.crowd.service.cache.CacheImpl;
import com.atlassian.crowd.service.cache.CachingGroupManager;
import com.atlassian.crowd.service.cache.CachingGroupMembershipManager;
import com.atlassian.crowd.service.cache.CachingUserManager;
import com.atlassian.crowd.service.cache.SimpleAuthenticationManager;
import com.atlassian.crowd.service.soap.client.SecurityServerClient;
import com.atlassian.crowd.service.soap.client.SecurityServerClientImpl;
import com.atlassian.crowd.service.soap.client.SoapClientPropertiesImpl;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
	    .csrf().disable()
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
    public static Properties getProps() throws IOException{
	 Properties prop = new Properties();
	try(InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("crowd.properties")){
	    prop.load(in);
	}
	return prop;
    }
    @Bean
    public SecurityServerClient securityServerClient() throws IOException{
	return new SecurityServerClientImpl(SoapClientPropertiesImpl.newInstanceFromProperties(getProps()));
    }
   private final BasicCache cache = new CacheImpl(Thread.currentThread().getContextClassLoader().getResource("crowd-ehcache.xml"));

    @Bean
    public AuthenticationManager crowdAuthenticationManager() throws IOException{

	    return new SimpleAuthenticationManager(securityServerClient());
    }
    @Bean
    public HttpAuthenticator httpAuthenticator() throws IOException{
	return new HttpAuthenticatorImpl(crowdAuthenticationManager());
    }
    @Bean
    public UserManager userManager() throws IOException{
	return new CachingUserManager(securityServerClient(), cache);
    }
    @Bean
    public GroupManager groupManager() throws IOException{
	return new CachingGroupManager(securityServerClient(), cache);
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	auth.authenticationProvider(crowdAuthenticationProvider());
    }
    @Bean
    public CrowdUserDetailsService crowdUserDetailsService() throws IOException{
	CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl();
	cusd.setUserManager(userManager());
	cusd.setGroupMembershipManager(new CachingGroupMembershipManager(securityServerClient(), userManager(),groupManager(),cache));
	cusd.setAuthorityPrefix("ROLE_");
	return cusd;
    }
    @Bean
    RemoteCrowdAuthenticationProvider crowdAuthenticationProvider() throws IOException{
	return new RemoteCrowdAuthenticationProvider(crowdAuthenticationManager(), httpAuthenticator(), crowdUserDetailsService());
    }
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("ROLE_USER");
    }
}

 

The hardest part was figuring out how to set up the beans, which was not documented at all, because before it was all XML magic. Now, assuming you have the proper crowd.properties file and the proper EhCache file setup (which could also be removed with java beans, but that's less egregious), you can use crowd integrations in a purely java fashion. 

 

The build.gradle to make this work, looks like the following: 

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.1.RELEASE")
    }
}
if (!hasProperty('mainClass')) {
    ext.mainClass = 'hello.Application'
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
    baseName = 'gs-securing-web'
    version =  '0.1.0'
}
repositories {
    maven {
	url = 'https://m2proxy.atlassian.com/repository/public'
    }
    mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
    compile "commons-codec:commons-codec:1.10"
    compile "org.springframework.boot:spring-boot-starter-web"
    compile "org.springframework.boot:spring-boot-starter"
    compile "org.springframework:spring-tx"
    compile 'org.springframework.boot:spring-boot-starter-security'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
    compile (group: "com.atlassian.crowd", name: "crowd-integration-springsecurity", version: "2.8.+"){
	exclude (group: 'org.apache.ws.commons');
    }
    compile 'org.slf4j:slf4j-api'
    compile "org.codehaus.groovy:groovy"
    compile "org.codehaus.groovy:groovy-json:2.3.8"
}
task wrapper(type: Wrapper) {
    gradleVersion = '2.10'
}

That's it! the authentication is then properly bootstrapped to make use of crowds springsecurity interface. Now, it's worth mentioning I haven't played around with how it hands me back various Roles/etc. but this should allow me to move forward. Also, this does not include the SSO code, which I haven't totally figured out yet. So once I get that I'll post a more thorough how-to somewhere. 

Andrus Suitsu September 8, 2016

Have you also got SSO working? I have tried to add a CrowdSSOAuthenticationProcessingFilter but with no luck so far.

Sampo Saarela October 4, 2017

I was too wondering that if you had any luck with the authentication filter?

willbdaniels April 10, 2018

Hi guys! I really apologize, I assumed Atlassian community would send me an e-mail if I had comments on my issues, which they (sadly) did not. Regardless, I'm no longer at the job that used the above configuration, and the need for the SSO portion of this integration never came up, as the users who were using this for delegated authentication weren't also users on our jira/confluence integration. This did work for authentication needs though for as long as I was with the company. Best of luck to you both! There are a few other people working on this integration it seems from my quick poking around recently. 

0 votes
Jonathan Turnock August 21, 2019

I have a simple solution working but there are a couple of bugs it seems. Checkout this thread for some more information, which also links a blog and associated git repo containing a working solution.

https://community.atlassian.com/t5/Crowd-questions/Integrating-Atlassian-Crowd-with-Spring-Boot-2-1-7-via-Spring/qaq-p/1159236#M4304

0 votes
Rafael Sanzio July 31, 2018

I've tried everything but there isn't working here, you have a GitHub of this code I want to see the code, That is important if I can make this work, The company that I work will start use spring-boot.

TAGS
AUG Leaders

Atlassian Community Events