Name

Crowd JAAS Login Module

Version

0.9.2

Product Versions

Crowd 1.0.6 (see comments for 1.6 support)

Author(s)

Brad Harvey

Homepage

 

Price

Free

License

BSD

JavaDocs

 

IssueTracking

 

Download JAR

CrowdJaasLoginModule-0.9.2.jar

Download Source

CrowdJaasLoginModule-0.9.2-src.zip

The JAAS Login Module is a Crowd Application Connector for JAAS and Spring Acegi.

Description/Features

(tick) Plug straight in to existing applications that use JAAS to take full advantage of Crowd's application provisioning and identity management features.

(tick) Enable single sign on in applications using the Spring Acegi security framework.

Usage

JAAS Login Module Options

The Crowd JAAS Login Module is configured via module options. These are set in an application specific manner (often with a Configuration file).

The options you need to set are:

  • crowd.server.url
  • application.name
  • application.password

This will allow you to authenticate against crowd with a user/password and retrieve the user's Crowd groups. These options can be set in the login module configuration, or in a crowd.properties file on the classpath. The remaining options must be set in the login module configuration.

If you want to authenticate with a token instead of password, set useToken.

Module Option

Description

Default

crowd.server.url

URL to Crowd web service

Defaulted from crowd.properties file

application.name

Crowd application name

Defaulted from crowd.properties file

application.password

Crowd application password

Defaulted from crowd.properties file

useToken

if true, treat the password as a token

false

If you are chaining login modules, tryFirstPass and friends can be used to share state between them. This is used where interactive callback handlers are employed to prevent the user being asked for their password multiple times.

Module Option

Description

Default

storePass

if true, save shared state if login succeeds (tryFirstPass and useFirstPass do not enable this automatically).

false

tryFirstPass

if true, attempt authentication from shared state. If this fails, try again with callback handler.

false

useFirstPass

if true, only attempt authentication from shared state - do not use callbackhandler at all.

false

clearPass

if true, clear shared state after commit

false

storeToken

if true and storePass is true, save the token to shared state instead of the password. Automatically true if useToken is true.

useToken ? true : false

Finally, if you need specific classes for principals and groups they can be specified with principalClassName and friends.

Module Option

Description

Default

principalClassName

class name of Principal implementation for User Principal

com.atlassian.crowd.application.jaas.CrowdPrincipal

roleGroupClassName

class name of Group implementation to contain roles

com.atlassian.crowd.application.jaas.CrowdGroup

roleGroupName

name of role Group

Roles

roleClassName

class name of Principal implementation for role Principals within the role group

value of principalClassName

JAAS in Acegi

See Acegi JAAS for instructions on using a JAAS login module in Acegi. Essentially, you need to configure the login module in a JaasAuthenticationProvider, and provide com.atlassian.crowd.application.acegi.CrowdAuthorityGranter as the AuthorityGranter to return the Crowd groups as granted authorities to Acegi.

Authenticate via Jaas
<bean id="jaasAuthenticationProvider" class="org.acegisecurity.providers.jaas.JaasAuthenticationProvider">
  <property name="loginConfig">
    <value>/WEB-INF/login.conf</value>
  </property>
  <property name="loginContextName">
    <value>crowd</value>
  </property>
  <property name="callbackHandlers">
    <list>
      <bean class="org.acegisecurity.providers.jaas.JaasNameCallbackHandler"/>
      <bean class="org.acegisecurity.providers.jaas.JaasPasswordCallbackHandler"/>
    </list>
  </property>
  <property name="authorityGranters">
    <list>
      <bean class="com.atlassian.crowd.application.acegi.CrowdAuthorityGranter"/>
    </list>
  </property>
</bean>	

The login config should look something like this:

/WEB-INF/login.conf
crowd {
	com.atlassian.crowd.application.jaas.CrowdLoginModule required application.name=acegi application.password=acegi crowd.server.url="http://localhost:8095/crowd/services/";
};

If your environment already uses JAAS, the loginConfig property may be ignored in favour of that environment's configuration mechanism. For example, in JBoss the login module has to be configured in conf/login-config.xml

Acegi Single Sign On

Single Sign On is implemented using the RememberMe infrastructure. It does not actually store the token information persistently for true remember me, and is not intended to.

The RememberMeServices implementation is com.atlassian.crowd.application.acegi.CrowdVerifyTokenService. This reads a crowd.properties file from the classpath (see Java Integration Libraries​).

<bean id="rememberMeServices" class="com.atlassian.crowd.application.acegi.CrowdVerifyTokenService">
    <constructor-arg value="changeThis"/> <!-- key must be shared with RememberMeAuthenticationProvider -->
</bean>

Place this snippet inside your authentication provider. The key must be shared with the rememberMeServices, but it isn't actually used to validate the cookie - Crowd has already validated it in the CrowdVerifyTokenService.

<bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
    <property name="key" value="changeThis"/>
</bean>

You will also need to add rememberMeServices references to various other beans. Here are the applicable beans in the acegi-tutorial configuration.

<!-- add remember me references to various other beans as per normal -->
	<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
		<constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
		<constructor-arg>
			<list>
				<ref bean="rememberMeServices"/>
				<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
			</list>
		</constructor-arg>
	</bean>

	<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager" ref="authenticationManager"/>
		<property name="authenticationFailureUrl" value="/acegilogin.jsp?login_error=1"/>
		<property name="defaultTargetUrl" value="/"/>
		<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
		<property name="rememberMeServices" ref="rememberMeServices"/>
	</bean>
   
	<bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
		<property name="authenticationManager" ref="authenticationManager"/>
		<property name="rememberMeServices" ref="rememberMeServices"/>
	</bean>

Examples

Simple JAAS Client

Here's a sample client. I created an application in Crowd called soapui with a password of soapui. This JAAS module configuration sets the application name, password, and Crowd server url.

crowd.conf
Crowd {
	com.atlassian.crowd.application.jaas.CrowdLoginModule required application.name=soapui application.password=soapui crowd.server.url="http://localhost:8095/crowd/services/";
};

Here's a snippet of code to load this configuration file from the classpath, create a login context, and login.

Sample Client Code to invoke a LoginContext
    URL resource = Thread.currentThread().getContextClassLoader().getResource("crowd.conf");
    System.setProperty("java.security.auth.login.config", URLDecoder.decode(resource.getFile()));
    LoginContext loginContext = new LoginContext("Crowd", new UsernamePasswordCallbackHandler("testuser", "password".toCharArray()));
    loginContext.login();
    System.out.println(loginContext.getSubject());
    loginContext.logout();

The UsernamePasswordCallbackHandler class is in the test directory of the source distribution. The output of that snippet is:

Subject:
	Principal: CrowdGroup: Roles [CrowdPrincipal: crowd-administrators, CrowdPrincipal: soapui]
	Principal: CrowdPrincipal: testuser

The user "testuser" has crowd-administrators and soapui groups in Crowd.

JBoss Login Module

To use the CrowdJAASLoginModule in JBoss, you need to change login-config.xml and copy the jar and dependencies into the server lib directory. I have tested with JBoss 4.05GA and JDK 1.5.

Here's an example application policy entry in login-config.xml. Adjust the module options as required.

    <application-policy name = "jmx-console">
       <authentication>
          <login-module code="com.atlassian.crowd.application.jaas.CrowdLoginModule" flag = "required">
           <module-option name="application.name">jboss</module-option>
           <module-option name="application.password">jboss</module-option>
           <module-option name="crowd.server.url">http://localhost:8095/crowd/services/</module-option>
          </login-module>
       </authentication>
    </application-policy>

In addition to CrowdJaasLoginModule-0.9.2.jar, the following jars are required in the <jboss home>/server/<context>/lib directory (eg, C:\jboss-4.0.5.GA\server\default\lib). All can be found in the client and client/lib directory under your Crowd installation.

  • crowd-core-1.0.6.jar
  • xfire-all-1.2.1.jar
  • stax-api-1.0.1.jar
  • wstx-asl-2.9.3.jar
  • jdom-1.0.jar
  • commons-httpclient-3.0.jar

This directory already contains a commons-httpclient.jar - this should be removed.

Sample Application

The Application Launchpad uses the JAAS and Acegi single sign on capabilities of the Crowd JAAS Login Module.

Version History

Version

Release Date

Description

0.9.2

April 29, 2007

Add Acegi support - see release notes in CrowdJaasLoginModule-0.9.2-src.zip

0.9.1

April 26, 2007

Minor bug fix and enhancement - see release notes in CrowdJaasLoginModule-0.9.1.zip

0.9

April 25, 2007

Initial Version.

Dependencies

  • Should be the same as the normal java integration client - crowd-core + jars found in $CROWD_INSTALL/client/lib.
  • I had to use xerces 2.8.1 to fix a class not found error.
  • The maven2 transitive dependencies report is quite scary due to xfire-all.

Known Limitations

The Crowd JAAS Login module uses the SecurityServerClient to communicate with Crowd. This class is implemented in a static manner, which means if you have multiple login modules defined they all share the same details for connecting to Crowd. This may not be suitable in a server environment.

Crowd roles and user attributes are not retrieved by the login module. In theory these could be retrieved and added to the subject as well.

The clearPass option will clear shared state added by other modules.

Logging out doesn't actually invalidate the token. In a single sign on environment this is probably a feature (smile)

Building from Source

This project uses maven2. I use the maven2 plugin for eclipse - http://m2eclipse.codehaus.org/.

You may need to add crowd-core to your local repository. Change to the $CROWD_INSTALL/client directory and execute this command:

mvn install:install-file -DgroupId=com.atlassian.crowd -DartifactId=crowd-core -Dversion=1.0.6 -Dpackaging=jar -Dfile=crowd-core-1.0.6.jar

References

JAAS Documentation

Acegi

16 Comments

  1. it seems this module doesn't work with Crowd 1.6; the PropertyUtils class in the Crowd client library has been moved. Is there any chance of an updated version for more recent Crowd versions?

    1. There's a small chance (smile) I don't use crowd myself and I haven't noticed much interest in this plugin, so I haven't attempted to keep it up to date. I think I saw that crowd now includes Spring Security integration, so I assume it is just the JAAS part you're after?

  2. I'm also trying to use this extension with a modern installation of Crowd (1.5.2, moving to 1.6 soon). I'm trying to configure JBoss so that access to the jmx-console uses Crowd for auth, much like your example above.

    I haven't tried using the specific versions of the libraries you've mentioned, yet, to see if they mitigate the problems I'm seeing, so I'll wait to post a bearing on success or failure. I just wanted to chime in and I'm interested in this plugin too!

    1. 0.9.2 definitely won't work with those version of Crowd - the Crowd API has changed. I'll have a look at what needs to change.

  3. CrowdJaasLoginModule-1.1.0-SNAPSHOT.jar contains the following changes from 0.9.2:

    + Remove acegi related classes and packages, as this functionality is now provided by crowd natively.
    + Update to Crowd 1.6.

    I've only had time to give it a very quick test with the "Simple JAAS Client" example, but it seems to work ok.

    1. Please contribute the sources for 1.1.0-SNAPSHOT or publish the scm location.

      1. Hi Daniel,

        It's already attached to the page - CrowdJaasLoginModule-1.1.0-SNAPSHOT-src.zip.

        Brad.

    2. Hey, thanks for the module. It seems to work with our Jboss (4.0.5.GA) and Crowd 1.6. After some bumps
      I got it to work.

      I have one question though. I was given the assignment to port our webapp called Marvin from ldap to crowd
      and we have a few roles coded into the application (MarvinUser for example). When logging in I get the following
      error message:

      Insufficient permissions, principal=..., requiredRoles=[MarvinUser, ...], principalRoles=[CrowdPrincipal: MarvinUser, ...]

      It seems that the result of the query to crowd is different than our ldap result. I notice the 'CrowdPrincipal: ' in front
      of the actual rolename. I don't have any knowledge of crowd itself and I don't have access to crowd management
      panels etc. Is there a way to let crowd return our required roles right? Or should I hack into the login module and
      change some code (don't realy want that ofcourse). I would like to avoid having to replace all our role validation
      through our application, because we need to be able to switch back to ldap if needed. So I'd like to keep the change
      as much to configuration as possible.

      I hope it makes sense, can somebody help?

      [Edit]

      Ok, Ive been debugging and traced it back into crowd itself. It has nothing to do with the plugin, thatone works fine.
      I'll post my question elsewhere. Again thanks for the plugin.

      [Edit2]

      Sorry, I have made quite a mess. Let me put things right.

      It was partly my own fault, but it was eventually still the module that didnt work properly.

      When getting things off the ground at first, I downloaded the sources to add some debug statements to see if the server responded correctly, it didnt, I had the wrong URL... I got it to work, but in the process downloaded the 0.9.2 sources instead of the snapshot sources and started working with that.

      After a lot of debugging, I noticed that the roles are of the class SimplePrincipal and are compared to the CrowdPrincipal class and fail. I tried to add the configuration option 'roleClassName' to org.jboss.security.SimplePrincipal, but to no succes. I ended up modifying the module source to generalize both to Principal and compare names to allow succes. And it works now.

      Weird thing is, I still use the 0.9.2 sources (modified) and it works, though our crowd server definately responds with: 'Version: 1.6 (Build:#390 - Dec 18, 2008)'. So it seems that the old module does work with the SimplePrincipal hack.... ? (Used the 1.0.6 client libs)

      Other weird thing is that I didnt get the configuration property to work, I asume thats what its there for.., got the same error as stated above.

      Last thing: I never got the snapshot to work and didnt find anything in the log (with the 1.6 client libs), maybe I was still missing some libraries...

      For now I will use our own modified 0.9.2 module, but I'd like some comments about the configuration of SimplePrincipal, because that would be nicer. And I hope this little work-log can be of use to someone else trying the same thing..

      1. Hi Bas,

        Glad you got it working. Are you using standard j2ee declarative role checking (eg config in web.xml / ejbs) or are they your own role checks?

        The equals implementation on CrowdPrincipal currently can only be equal to another CrowdPrincipal. This is consistent with some other principal implementations I used as a reference (eg com.sun.security.auth.UnixPrincipal), but I think the jboss SimplePrincipal allows any Principal implementation with the same name. I think that would be fine in this case too.

        I also notice the CrowdGroup isMember implementation is a bit naive as it can't handle nested groups. Don't think that's a problem in this case, but you never know.

        The intention of the roleGroupName etc configs was to allow what you were trying to do. Were you specifying it in crowd.properties or as login module options? If you specify a class that doesn't exist do you at least get an error from that?

        My time is a bit thin this days, but I'll look at fixing CrowdPrincipal & CrowdGroup to be more forgiving, and add a bit more debug logging around the options.

        Cheers,
        Brad.

      2. I've uploaded new CrowdJaasLoginModule-1.1.0-SNAPSHOT.jar and CrowdJaasLoginModule-1.1.0-SNAPSHOT-src.zip which I hope will resolve your problems. There was a silly bug (missing call to super procedure) that prevented those additional module options from working, and I've modified the principal class equals method to only check the name.

        + Bug Fix - CrowdGroup#isMember now does recursive search as per interface javadoc. Equals method is now more restrictive and checks group membership as well as name.
        + Bug Fix - CrowdPrincipal#equals can be equal to other Principal implementations
        + Bug Fix - PrincipalClassName, RoleClassName, RoleGroupClassName and RoleGroupName module options now work.

        Let me know how it goes.
        Cheers,
        Brad.

        1. Hey Brad,

          Thanks for looking into this. I didnt have time until today to check it out, sorry for that.

          Unfortunately I have to tell you that it still doesnt work like it should. I have put your new jar and all the 1.6 client libs inside my lib directory and set the roleClassName in my crowd.properties file to: org.jboss.security.SimplePrincipal. When I log in, I still get my initial error message. This is the log that I get:

          (I removed some names for the sake of security, replaced with ...)

          10:12:49,844 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'application.name' : '...'
          10:12:49,845 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'application.login.url' : 'https://...'
          10:12:49,845 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'crowd.server.url' : 'https://...'
          10:12:49,845 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'session.isauthenticated' : 'session.isauthenticated'
          10:12:49,846 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'session.tokenkey' : 'session.tokenkey'
          10:12:49,846 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'session.lastvalidation' : 'session.lastvalidation'
          10:12:49,847 INFO  [ClientPropertiesImpl.loadAndLogPropertyString-127] Loading property: 'session.validationinterval' : '0'
          10:12:50,862 INFO  [SecurityServerClientImpl.getApplicationToken-155] Existing application token is null, authenticating ...
          10:12:51,536 INFO  [HttpMethodBase.readResponse-1598] Discarding unexpected response: HTTP/1.1 100 Continue
          10:12:51,622 INFO  [SecurityServerClientImpl.getApplicationToken-159] Using existing token: 5...
          10:12:51,656 INFO  [HttpMethodBase.readResponse-1598] Discarding unexpected response: HTTP/1.1 100 Continue
          10:12:51,870 INFO  [HttpMethodBase.readResponse-1598] Discarding unexpected response: HTTP/1.1 100 Continue
          10:12:52,472 ERROR [RoleBasedAuthorizationInterceptor.invoke-148] Insufficient permissions, principal=bas, requiredRoles=[MarvinUser, ...], principalRoles=[CrowdPrincipal: MarvinUser, ...]

          Since we have updated our production environment to my earlier hack, my time on this issue is now limited. I hope my message can provide you with some information, I can probably run a few other tests if it could be usefull...

          Let me know,

          Bas

          1. Hi Bas,

            Can you set those properties in the login module options instead of crowd.properties please? Also, if you can enable DEBUG level logging it will now show some extra debug about the options being used.

            Thanks,
            Brad.

            1. Hey Brad,
              It works!

              I tried out the module options instead of the crowd.properties as you said and I can login now. I didnt set the logging for debug yet, but there is probably no need to do that. I think you should try to fix the crowd.properties...

              Regards,
              Bas

  4. Hi Brad,

    First of all, many thanks for creating this plugin. This is exactly what I need! I want HTTP authentication when my WSDL is accessed. This authentication should be done with the user credentials from crowd. Nevertheless I can't get things to work.

    I'm using JBoss 4.2.3 and partially followed your instructions to configure it. I did the following things in my code. Can you possibly check what I'm doing wrong, or what I'm forgetting?

    These are the annotations I have on my web service bean:

    @Stateless
    @SecurityDomain(value="mydomain")
    @SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)
    @WebService(name = "ProjectWebService", targetNamespace = "http://...", serviceName = "myservice")
    @WebContext(urlPattern = "/projectservice")
    

    login-config.xml (server\default\conf)

     <application-policy name = "mydomain">
           <authentication>
              <login-module code="com.atlassian.crowd.application.jaas.CrowdLoginModule" flag = "required">
                 <module-option name="application.name">.</module-option>
                 <module-option name="application.password">.</module-option>
                 <module-option name="crowd.server.url">http://ip/crowd/services/</module-option>
              </login-module>
           </authentication>
      </application-policy>
    

    and web.xml

     <security-constraint>
    	<web-resource-collection>
    		<web-resource-name>projectservice WSDL</web-resource-name>
    		<url-pattern>/projectservice</url-pattern>
    	</web-resource-collection>
    	<auth-constraint />
    </security-constraint>
    <login-config>
    	<auth-method>BASIC</auth-method>
    </login-config>
    

    This is all I've done. When I test with SoapUI, well, no authentication happens. Any ideas on what causes this?

    Thx,

    Tim

    1. Hi Tim,

      I think you might need to specify a role in the auth-constraint element. For web apps I seem to recall having a jboss-web.xml with an auth domain mapping, but not sure. Perhaps the SecurityDomain annotation does that part.

      Hope that helps,
      Brad.

  5. Thanks for your reply Brad. Finally, I've managed to get JBoss calling the Crowd LoginModule. This is in my logs:

    2009-08-19 13:12:51,113 DEBUG [com.atlassian.crowd.application.jaas.CrowdLoginModule] Initialise2009-08-19 13:12:51,129 DEBUG [com.atlassian.crowd.application.jaas.CrowdLoginModule] {jboss.security.security_domain=mydomain, application.name=admin, application.password=admin, crowd.server.url=http://localhost:8095/crowd/services}2009-08-19 13:12:51,254 DEBUG [com.atlassian.crowd.application.jaas.CrowdLoginModule] Abort
    Any idea why the call is immediately aborted? I've checked your sources and this method only gets called in the tests...
    

    I was able to debug your source code and found out that he does not get passed line 62 in the CrowdLoginModule class. The line of code:

    securityServerClient = new SecurityServerClientImpl(new ClientPropertiesImpl(properties));
    

    The only thing I can make out of this, is that the connection to my crowd server fails.An important note, I'm using Crowd 1.5.1 and not 1.0.6. This is what my login-config.xml entry looks like:

    <application-policy name="mydomain">
           <authentication>
              <login-module code="com.atlassian.crowd.application.jaas.CrowdLoginModule" flag = "required">
               <module-option name="application.name">admin</module-option>
               <module-option name="application.password">admin</module-option>
               <module-option name="crowd.server.url">http://mycrowd:8095/crowd/services</module-option>
              </login-module>
           </authentication>
        </application-policy>

    This is the correct "crowd.server.url" property value right or do I need to point to a different URL? Any advice very, very welcome!

    EDIT: NEVERMIND. This early exit was caused by a missing commons-lang jar in the JBoss lib folder. Now it works like a charm! (big grin)(thumbs up)