Use JAAS for LDAP password with Spring security
Asked Answered
A

1

4

I have a Java EE web application which uses an LDAP authentication. I use Spring security to connect to my LDAP with the following code:

<bean id="ldapContextSource" class="com.myapp.security.authentication.MySecurityContextSource">
    <constructor-arg index="0" value="${ldap.url}" />
    <constructor-arg index="1" ref="userConnexion" />
</bean>

<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="ldapAuthProvider" />
</security:authentication-manager>

<bean id="userConnexion" class="com.myapp.util.security.WebsphereCredentials">
    <constructor-arg value="${ldap.authJndiAlias}" />
</bean>

<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <constructor-arg>
        <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
            <constructor-arg ref="ldapContextSource" />
            <property name="userSearch" ref="userSearch" />
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="com.myapp.security.authentication.MyAuthoritiesPopulator" >
            <property name="userService" ref="userService" />
        </bean>
    </constructor-arg>
    <property name="userDetailsContextMapper" ref="myUserDetailsContextMapper"/>
    <property name="hideUserNotFoundExceptions" value="false" />
</bean>

Actually, my bean WebsphereCredentials uses a WebSphere private class WSMappingCallbackHandlerFactory as in this response : How to access authentication alias from EJB deployed to Websphere 6.1

We can see it in the official websphere documentation: http://pic.dhe.ibm.com/infocenter/wasinfo/v6r1/index.jsp?topic=%2Fcom.ibm.websphere.express.doc%2Finfo%2Fexp%2Fae%2Frsec_pluginj2c.html

But I don't want it because:

  1. I think my application can access all JAAS logins in my WebSphere instance (not sure).
  2. This class is defined in the HUGE IBM client library com.ibm.ws.admin.client-7.0.0.jar (42 Mo) => compilation slower, not present in my enterprise nexus
  3. It's not portable, not standard

For information, I define the WebsphereCredentials constructor as this:

Map<String, String> map = new HashMap<String, String>();

map.put(Constants.MAPPING_ALIAS, this.jndiAlias);
Subject subject;
try {
    CallbackHandler callbackHandler = WSMappingCallbackHandlerFactory.getInstance().getCallbackHandler(map, null);
    LoginContext lc = new LoginContext("DefaultPrincipalMapping", callbackHandler);
    lc.login();
    subject = lc.getSubject();
} catch (NotImplementedException e) {
    throw new EfritTechnicalException(EfritTechnicalExceptionEnum.LOGIN_CREDENTIAL_PROBLEM, e);
} catch (LoginException e) {
    throw new EfritTechnicalException(EfritTechnicalExceptionEnum.LOGIN_CREDENTIAL_PROBLEM, e);
}

PasswordCredential cred = (PasswordCredential) subject.getPrivateCredentials().toArray()[0];

this.user = cred.getUserName();
this.password = String.valueOf(cred.getPassword());

Is there a way to use just Spring security and remove this dependency?

I have no idea how to combine http://static.springsource.org/spring-security/site/docs/3.1.x/reference/jaas.html and http://static.springsource.org/spring-security/site/docs/3.1.x/reference/ldap.html.

Maybe I must totally change my approach and use another way?

Aragats answered 26/12, 2012 at 10:10 Comment(2)
>HUGE IBM client library com.ibm.ws.admin.client-7.0.0.jar (42 Mo) => compilation slower - Though I understand the need to avoid such large jars, normally having them will not slow down compilation. Java does not recompile jars. They are linked in, but not compiled.Edi
Yes you are right. I write it to quickly...I say it because when I finish to compile I copy my jars for the deploy. But this jars is provide by the application server and I already flag it as provide in maven pom file. ;o)Aragats
S
5

I assume your goal is to simply utilize the username / password that you configure in WebSphere to connect to the LDAP directory? If this is the case, you are not really trying to combine LDAP and JAAS based authentication. The JAAS support is really intended to be a way of using JAAS LoginModules to authenticate a user instead of using the LDAP based authentication.

If you are wanting to obtain the username and password without having a compile time dependency on WebSphere, you have a few options.

Eliminating Compile Time and Runtime Dependencies on WAS

One option is to configure the password in a different way. This could be as simple as using the password directly directly in the configuration file as shown in the Spring Security LDAP documentation:

<bean id="ldapContextSource"
        class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
  <property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
  <property name="password" value="password"/>
</bean>

You could also configure the username password in JNDI. Another alternative is to use a .properties file with the Property. If you are wanting to ensure the password is secured, then you will probably want to encrypt the password using something like Jasypt.

Eliminating Compile Time dependencies and still configuring with WAS

If you need or want to use WebSphere's J2C support for storing the credentials, then you can do by injecting the CallbackHandler instance. For example, your WebsphereCredentials bean could be something like this:

try {
    LoginContext lc = new LoginContext("DefaultPrincipalMapping", this.callbackHandler);
    lc.login();
    subject = lc.getSubject();
} catch (NotImplementedException e) {
    throw new EfritTechnicalException(EfritTechnicalExceptionEnum.LOGIN_CREDENTIAL_PROBLEM, e);
} catch (LoginException e) {
    throw new EfritTechnicalException(EfritTechnicalExceptionEnum.LOGIN_CREDENTIAL_PROBLEM, e);
}

PasswordCredential cred = (PasswordCredential) subject.getPrivateCredentials().toArray()[0];

this.user = cred.getUserName();
this.password = String.valueOf(cred.getPassword());

Your configuration would then look something like this:

<bean id="userConnexion" class="com.myapp.util.security.WebsphereCredentials">
    <constructor-arg ref="wasCallbackHandler"/>
</bean>

<bean id="wasCallbackHandler"
      factory-bean="wasCallbackFactory"
      factory-method="getCallbackHandler">
    <constructor-arg>
      <map>
        <entry
            value="${ldap.authJndiAlias}">
          <key>
            <util:constant static-field="com.ibm.wsspi.security.auth.callback.Constants.MAPPING_ALIAS"/>
          </key>
        </entry>
      </map>
    </constructor-arg>
    <constructor-arg>
      <null />
    </constructor-arg>
</bean>

<bean id="wasCallbackFactory"
    class="com.ibm.wsspi.security.auth.callback.WSMappingCallbackHandlerFactory"
    factory-method="getInstance" />

Disclaimer

CallbackHandler instances are not Thread safe and generally should not be used more than once. Thus it can be a bit risky injecting CallbackHandler instances as member variables. You may want to program in a check to ensure that the CallbackHandler only used one time.

Hybrid Approach

You could do a hybrid approach that always removes the compile time dependency and allows you to remove the runtime dependency in instances where you might not be running on WebSphere. This could be done by combining the two suggestions and using Spring Bean Definition Profiles to differentiate between running on WebSphere and a non-WebSphere machine.

Stilt answered 27/12, 2012 at 20:24 Comment(3)
Thanks for your reply! In fact yes, I want to remove my password in configuration file. I provide configuration file to my deploy team but I don't know the production password that's why I cannot have the password. For local and dev env. I already use Jasypt and spring profil depending on my environment. I like the idea to define my dependency in my spring configuration. Sorry to don't have it. I have already propose to my deploy team to store password in JNDI but they don't want it ;o( . Many thanks for your reply.Aragats
small change, I remove the first factory-method="getCallbackHandler" in the bean userConnexion. The rest is ok, thanks againAragats
Glad to hear it helped. I updated the post to reflect the small change you mentioned.Stilt

© 2022 - 2024 — McMap. All rights reserved.