Initialization error of custom login module
code-monkey Mar 26, 2014 12:25 PMI have implemented a custom Login-Module for JBoss 6 which extends AbstractServerLoginModule. Unfortunately it fails to initialize after the first request on a protected page.
This is the correpsonding section of my server.log:
2014-03-25 15:14:08,159 DEBUG [org.apache.catalina.session.ManagerBase#processExpires] (ContainerBackgroundProcessor[StandardEngine[jboss.web]]) processExpires End expire sessions StandardManager processingTime 0 expired sessions: 0 2014-03-25 15:14:12,260 TRACE [org.jboss.security.SecurityRolesAssociation#setSecurityRoles] (http-127.0.0.1-21000-1) setSecurityRoles Setting threadlocal:{} 2014-03-25 15:14:12,262 DEBUG [org.apache.catalina.authenticator.AuthenticatorBase#invoke] (http-127.0.0.1-21000-1) invoke Security checking request GET /powerpanel/pages/login.xhtml 2014-03-25 15:14:12,263 DEBUG [org.apache.catalina.realm.RealmBase#findSecurityConstraints] (http-127.0.0.1-21000-1) findSecurityConstraints Checking constraint 'SecurityConstraint[powerpanel]' against GET /pages/login.xhtml --> true 2014-03-25 15:14:12,263 DEBUG [org.apache.catalina.realm.RealmBase#findSecurityConstraints] (http-127.0.0.1-21000-1) findSecurityConstraints Checking constraint 'SecurityConstraint[powerpanel]' against GET /pages/login.xhtml --> true 2014-03-25 15:14:12,263 DEBUG [org.apache.catalina.authenticator.AuthenticatorBase#invoke] (http-127.0.0.1-21000-1) invoke Calling hasUserDataPermission() 2014-03-25 15:14:12,264 DEBUG [org.apache.catalina.realm.RealmBase#hasUserDataPermission] (http-127.0.0.1-21000-1) hasUserDataPermission User data constraint has no restrictions 2014-03-25 15:14:12,277 DEBUG [org.jboss.security.integration.JNDIBasedSecurityManagement#createSecurityDomainContext] (http-127.0.0.1-21000-1) createSecurityDomainContext Creating SDC for domain=i24-panel-ws-auth 2014-03-25 15:14:12,278 TRACE [org.jboss.security.integration.JNDIBasedSecurityManagement#lookUpJNDI] (http-127.0.0.1-21000-1) lookUpJNDI Look up of JNDI for i24-panel-ws-auth/authorizationMgr failed with org.jboss.security.plugins.JaasSecurityManager cannot be cast to org.jboss.security.AuthenticationManager 2014-03-25 15:14:12,279 TRACE [org.jboss.security.integration.JNDIBasedSecurityManagement#getAuthorizationManager] (http-127.0.0.1-21000-1) getAuthorizationManager Exception in getting authorization mgr: java.lang.NullPointerException at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:881) [:1.6.0_35] at org.jboss.security.integration.JNDIBasedSecurityManagement.getAuthorizationManager(JNDIBasedSecurityManagement.java:172) [:6.0.0.Final] at org.jboss.security.plugins.JBossSecurityContext.getAuthorizationManager(JBossSecurityContext.java:268) [:6.0.0.Final] at org.jboss.security.plugins.javaee.WebAuthorizationHelper.hasUserDataPermission(WebAuthorizationHelper.java:183) [:3.0.0.CR2] at org.jboss.web.tomcat.security.JBossWebRealm.hasUserDataPermission(JBossWebRealm.java:668) [:6.0.0.Final] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:529) [:6.0.0.Final] at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285) [:1.1.0.Final] at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261) [:1.1.0.Final] at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88) [:6.0.0.Final] at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100) [:6.0.0.Final] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) [:6.0.0.Final] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [:6.0.0.Final] at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158) [:6.0.0.Final] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [:6.0.0.Final] at org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53) [:6.0.0.Final] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362) [:6.0.0.Final] at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [:6.0.0.Final] at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654) [:6.0.0.Final] at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951) [:6.0.0.Final] at java.lang.Thread.run(Thread.java:662) [:1.6.0_35] 2014-03-25 15:14:12,285 TRACE [org.jboss.security.SecurityRolesAssociation#setSecurityRoles] (http-127.0.0.1-21000-1) setSecurityRoles Setting threadlocal:null 2014-03-25 15:14:12,286 ERROR [org.apache.catalina.connector.CoyoteAdapter#service] (http-127.0.0.1-21000-1) service An exception or error occurred in the container during the request processing: java.lang.IllegalStateException: Authorization Manager is null at org.jboss.security.plugins.javaee.WebAuthorizationHelper.hasUserDataPermission(WebAuthorizationHelper.java:185) [:3.0.0.CR2] at org.jboss.web.tomcat.security.JBossWebRealm.hasUserDataPermission(JBossWebRealm.java:668) [:6.0.0.Final] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:529) [:6.0.0.Final] at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285) [:1.1.0.Final] at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261) [:1.1.0.Final] at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88) [:6.0.0.Final] at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100) [:6.0.0.Final] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) [:6.0.0.Final] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [:6.0.0.Final] at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158) [:6.0.0.Final] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [:6.0.0.Final] at org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53) [:6.0.0.Final] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362) [:6.0.0.Final] at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [:6.0.0.Final] at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654) [:6.0.0.Final] at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951) [:6.0.0.Final] at java.lang.Thread.run(Thread.java:662) [:1.6.0_35]
Obviously the AuthorizationManager cannot be created from the JaasSecurityManager but I'm not getting the reason for this. I thought this would be handled transparently by the SX framework as long as I provide a proper extension/implementation of one of the existing JBoss Login-Modules.
Has anybody tried to extend AbstractServerLoginModule with success or had a similar problem? Also, any clarification of the issue would be highly appreciated!
Here is how I set up the Login-Module:
The implementing class is part of the web-application which I'm trying to secure
package de.ig.ui.kundenpanel.authorization; import java.io.IOException; import java.security.Principal; import java.security.acl.Group; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.spi.AbstractServerLoginModule; import de.ig.common.bo.i24.authentication.AuthLoginRequest; import de.ig.common.bo.i24.authentication.AuthLoginResponse; import de.ig.common.bo.userlogin.UserLogin; import de.ig.common.exception.SoapClientException; import de.ig.common.exception.TechnicalException; import de.ig.common.tool.communication.IgServiceLocator; import de.ig.common.tool.communication.SessionKeyAuthParams; import de.ig.common.tool.communication.rmi.delegator.I24AuthenticationClientRMILocal; import de.ig.common.tool.log.TransactionItem; import de.ig.ui.kundenpanel.utility.TransactionItemFactory; /** * Custom login module that authenticates against I24 Panel Authentication * Web-Service. * * @author mthielsch * */ public class I24PanelLoginModule extends AbstractServerLoginModule { private static final String I24_AUTHENTICATION_CLIENT = "i24_authentication_client"; /** * The login identity */ private UserContext identity; /** * The user roles */ private Set<Principal> principalRoles = new HashSet<Principal>(); // user credentials private String userName; private String password; private String callerIpAddress; private I24AuthenticationClientRMILocal rmiAuthenticationClientDelegator = (I24AuthenticationClientRMILocal) IgServiceLocator .getInstance().getService(IgServiceLocator.I24_AUTHENTICATION_SERVICE_LOCAL); public I24PanelLoginModule() { super(); } @SuppressWarnings("rawtypes") @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { super.initialize(subject, callbackHandler, sharedState, options); } @Override public boolean login() throws LoginException { log.trace("login"); loginOk = false; try { // If useFirstPass is true, look for the shared password if (useFirstPass == true) { initCredentialsFromSharedState(); } else { // Use credentials from callback handler initCredentialsFromCallbackHandler(); } if (userName != null && password != null) { AuthLoginRequest authLoginRequest = new AuthLoginRequest(); authLoginRequest.setUsername(userName); authLoginRequest.setPassword(password); authLoginRequest.setIp(callerIpAddress); TransactionItem track = TransactionItemFactory.getTransactionItem(); SessionKeyAuthParams authParams = new SessionKeyAuthParams(null); authParams.setTransactionItem(track); AuthLoginResponse authLoginResponse = rmiAuthenticationClientDelegator.login(track, authParams, authLoginRequest); this.identity = createIdentity(userName, password, authLoginResponse.getPermissions(), authLoginResponse.getAuthKey(), authLoginResponse.getCustomer(), callerIpAddress); for (String role : authLoginResponse.getPermissions()) { principalRoles.add(new SimplePrincipal(role)); } loginOk = true; return true; } // Else, fall through and perform the login } catch (UnsupportedCallbackException uce) { log.error("CallbackHandler failed to provide login credentials", uce); throw new LoginException("CallbackHandler failed to provide login credentials"); } catch (SoapClientException sce) { log.error("Calling I24 Authentication Web-Service failed", sce); throw new LoginException("Calling I24 Authentication Web-Service failed"); } catch (TechnicalException te) { log.error("Calling I24 Authentication Web-Service failed", te); throw new LoginException("Calling I24 Authentication Web-Service failed"); } catch (IOException ioe) { log.error("Got IOException during login process", ioe); throw new LoginException("Login failed while reading provided credentials"); } return false; } private void initCredentialsFromCallbackHandler() throws IOException, UnsupportedCallbackException { Callback[] callbacks = new Callback[3]; callbacks[0] = new NameCallback("username"); callbacks[1] = new PasswordCallback("password", false); callbacks[2] = new IPAddressCallback(); callbackHandler.handle(callbacks); userName = ((NameCallback) callbacks[0]).getName(); password = new String(((PasswordCallback) callbacks[1]).getPassword()); callerIpAddress = ((IPAddressCallback) callbacks[2]).getIpAddress(); } private void initCredentialsFromSharedState() { Object userNameObject = sharedState.get("javax.security.auth.login.name"); Object passwordObject = sharedState.get("javax.security.auth.login.password"); userName = extractUserName(userNameObject); password = extractPassword(passwordObject); } private String extractUserName(Object userNameObject) { if (userNameObject instanceof Principal) { return ((Principal) userNameObject).getName(); } else if (userNameObject != null) { return userNameObject.toString(); } return null; } private String extractPassword(Object passwordObject) { if (passwordObject instanceof char[]) return new String((char[]) passwordObject); else if (passwordObject != null) { return passwordObject.toString(); } return null; } private UserContext createIdentity(String userName, String password, List<String> roles, String sessionToken, String customerId, String ipAddress) throws LoginException { try { UserContext identity = new UserContext(); identity.setPrincipalName(userName); identity.setSessionToken(sessionToken); identity.setRemoteAddr(ipAddress); UserLogin userLogin = new UserLogin(); userLogin.setUserName(userName); userLogin.setPassword(password); userLogin.setRoles(roles); userLogin.setCustomerId(customerId); identity.setUserLogin(userLogin); return identity; } catch (Exception e) { log.debug("Failed to create identity", e); throw new LoginException("Failed to create principal: " + e.getMessage()); } } @Override public boolean commit() throws LoginException { if (loginOk == false) return false; // Add subject principals Set<Principal> principals = subject.getPrincipals(); if (!principals.contains(identity)) { principals.add(identity); } Group[] roleSets = getRoleSets(); for (int g = 0; g < roleSets.length; g++) { Group group = roleSets[g]; String name = group.getName(); Group subjectGroup = createGroup(name, principals); // Copy the group members to the Subject group Enumeration<? extends Principal> members = group.members(); while (members.hasMoreElements()) { Principal role = (Principal) members.nextElement(); subjectGroup.addMember(role); } } return true; } @Override protected Principal getIdentity() { return identity; } @Override public boolean logout() throws LoginException { subject.getPrincipals().remove(this.identity); this.identity = null; this.userName = null; this.password = null; this.callerIpAddress = null; this.principalRoles = null; return true; } @Override protected Group[] getRoleSets() throws LoginException { Group roles = new SimpleGroup("Roles"); for (Principal role : principalRoles) { roles.addMember(role); } return new Group[] { roles }; } }
Definition of the module within /server/default/conf/login-config.xml is as follows
<application-policy name="i24-panel-ws-auth">
<authentication>
<login-module code="de.ig.ui.kundenpanel.authorization.I24PanelLoginModule" flag="required"> </login-module>
</authentication>
</application-policy>
Which is then referenced in the jboss-web.xml within the WAR
<jboss-web>
<security-domain>i24-panel-ws-auth</security-domain>
<context-root>myContextRoot</context-root>
</jboss-web>
And finally the security configuration within the web.xml:
<welcome-file-list> <welcome-file>/pages/login.xhtml</welcome-file> </welcome-file-list> <login-config> <auth-method>FORM</auth-method> <realm-name>i24-panel-ws-auth</realm-name> <form-login-config> <form-login-page>/pages/login.xhtml</form-login-page> <form-error-page>/pages/login.xhtml</form-error-page> </form-login-config> </login-config> <security-role> <role-name>AuthAll</role-name> </security-role> <security-constraint> <web-resource-collection> <web-resource-name>myContextRoot</web-resource-name> <url-pattern>/pages/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>AuthAll</role-name> </auth-constraint> </security-constraint>