[jboss-user] [Security & JAAS/JBoss] - JavaServer Faces and container-managed authorization not wor

poesys do-not-reply at jboss.com
Fri Aug 15 18:08:06 EDT 2008


I'm working with JavaServer Faces and JBoss and trying to set up JAAS and container-managed authorization using roles.

My problem is that, after I successfully authenticate using a login form and create a login context with a subject through a custom login module (all of which works fine), the Faces navigation tries to take me to the myHome page and fails, redisplaying the login page. The myHome.xhtml page is in the directory specified in the <security-constraint> element in web.xml and requires the community_user role, which I've verified is being correctly put into the Roles group in the subject. It's definitely seeing the constraint, which has CONFIDENTIAL to require SSL, and the login page is coming back with the myHome.faces url using the SSL server address and port. All my Faces navigation rules use the  option.

The only anomaly with respect to the many examples I've seen is in the realm name. This might be a red herring, not sure. The jboss-web.xml file contains the following:


  | <jboss-web>
  |     <security-domain>java:/jaas/tairweb</security-domain>
  | 	<context-root>/</context-root>
  | 	<virtual-host>bob</virtual-host>
  | 	<resource-ref>
  | 		<res-ref-name>jdbc/ReadOnly</res-ref-name>
  | 		<jndi-name>java:/jdbc/ReadOnlyTairTestJTDS</jndi-name>
  | 	</resource-ref>
  | 	<resource-ref>
  | 		<res-ref-name>jdbc/WebWriter</res-ref-name>
  | 		<jndi-name>java:/jdbc/WebWriterTairTestJTDS</jndi-name>
  | 	</resource-ref>
  | </jboss-web>
  | 

The login-config.xml file in server/default/conf contains this:

  |     <application-policy name="java:/jaas/tairweb">
  |         <authentication>
  |       <!-- Add this line to your login-config.xml to include the ClientLoginModule propogation,
  |            which propagates the login context to the security "interceptor" subsystem in JBoss;
  |            without this, JBoss won't have the Subject with which to compare roles. -->      
  |       <login-module code="org.jboss.security.ClientLoginModule" flag="required" >
  |                 <module-option name="password-stacking">useFirstPass</module-option>
  |                 <module-option name="restore-login-identity">true</module-option>
  |       </login-module>
  |             <login-module code="org.tair.community.login.TairJbossLoginModule" flag="required">
  |                 <module-option name="debug">true</module-option>
  |                 <module-option name="user_query">
  |                     SELECT c.community_id, p.is_tair_curator, p.is_external_curator, 
  |                            p.first_name, p.person_id, c.status 
  |                       FROM Community c JOIN 
  |                            Person p ON c.community_id = p.community_id 
  |                      WHERE c.is_obsolete = 'F' AND 
  |                            c.user_name = ? AND
  |                            c.password = ?
  |                 </module-option>
  |             </login-module>
  |         </authentication>
  |     </application-policy>
  | 

and, finally, the web.xml file contains this:

  |     <login-config>
  |         <auth-method>FORM</auth-method>
  |         <realm-name>tairweb</realm-name>
  |         <form-login-config>
  |             <form-login-page>/community/login/login.faces</form-login-page>
  |             <form-error-page>/community/login/login-failed.faces</form-error-page>
  |         </form-login-config>
  |     </login-config>
  | 

The anomaly is in the name. In all the examples I've seen, the login-config.xml file application-policy is just the name without the JNDI prefix, which is present only in the security-domain in jboss-web.xml. If I do that, JBoss tries for the "other" login module and doesn't find the user.properties file and gives an error. As written above, I get the login form correctly. I don't know if this is a change in JBoss behavior with 4.2.2 or what, or whether this is a symptom of something wrong.

Here's the security constraint from web.xml:

  |     <security-role>
  |         <description>A community user</description>
  |         <role-name>community_user</role-name>
  |     </security-role>
  |     <security-constraint>
  |         <display-name>Community</display-name>
  |         <web-resource-collection>
  |             <web-resource-name>CommunityPages</web-resource-name>
  |             <description>All Faces pages available only to community users</description>
  |             <url-pattern>/community/community_user/*</url-pattern>
  |         </web-resource-collection>
  |         <auth-constraint>
  |             <role-name>community_user</role-name>
  |         </auth-constraint>
  |         <user-data-constraint>
  |             <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  |         </user-data-constraint>
  |     </security-constraint>
  | 

Here's the Faces navigation code from faces-config.xml:

  |     <navigation-rule>
  |         <from-view-id>/community/login/login.xhtml</from-view-id>
  |         <navigation-case>
  |             <from-outcome>success</from-outcome>
  |             <to-view-id>/community/community_user/myHome.xhtml</to-view-id>
  |             <redirect />
  |         </navigation-case>
  |         <navigation-case>
  |             <from-outcome>failure</from-outcome>
  |             <to-view-id>/community/login/login.xhtml</to-view-id>
  |             <redirect/>
  |         </navigation-case>
  |         <navigation-case>
  |             <from-outcome>request_info</from-outcome>
  |             <to-view-id>/community/registration/requestInfo.xhtml</to-view-id>
  |             <redirect />
  |         </navigation-case>
  |         <navigation-case>
  |             <from-outcome>register</from-outcome>
  |             <to-view-id>/community/registration/register.xhtml</to-view-id>
  |             <redirect />
  |         </navigation-case>
  |     </navigation-rule>
  | 

Finally, in case it's important, here's the code for the login module:

  | public class TairJbossLoginModule extends AbstractServerLoginModule implements
  |     LoginModule {
  |   /** TAIR user object */
  |   private User user = null;
  |   /** debug setting based on the configuration file stanza debug setting */
  |   private boolean debug = false;
  |   /** The query to use to retrieve the user by username and password */
  |   private String query;
  | 
  |   // error messages
  | 
  |   private static final String NULL_SUBJECT = "Null subject in Login Module";
  |   private static final String NULL_HANDLER =
  |       "Null callback handler in Login Module";
  |   private static final String NULL_OPTIONS = "Null options map in Login Module";
  |   private static final String NULL_QUERY =
  |       "No login query supplied in conf/login-config.xml";
  |   private static final String NOT_YET_ACTIVE =
  |       "Account is not yet activated by TAIR";
  |   private static final String INVALID_USER =
  |       "Invalid username or password, please reenter the correct values and submit again";
  |   private static final String FAILURE = "Could not log in user";
  |   private static final String ERROR_PREFIX = "Login Error";
  |   private static final String WARN_PREFIX = "Login Notification";
  | 
  |   /*
  |    * (non-Javadoc)
  |    * 
  |    * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
  |    *      javax.security.auth.callback.CallbackHandler, java.util.Map,
  |    *      java.util.Map)
  |    */
  |   @SuppressWarnings("unchecked")
  |   public void initialize(Subject subject, CallbackHandler handler,
  |                          Map sharedState, Map options) {
  |     // Check the inputs for nulls.
  |     if (subject == null) {
  |       throw new IllegalArgumentException(NULL_SUBJECT);
  |     }
  |     if (handler == null) {
  |       throw new IllegalArgumentException(NULL_HANDLER);
  |     }
  |     if (options == null) {
  |       throw new IllegalArgumentException(NULL_OPTIONS);
  |     }
  | 
  |     // Call the superclass initializer to set everything.
  |     super.initialize(subject, handler, sharedState, options);
  | 
  |     // Extract debug and query custom fields.
  |     String debugValue = (String)this.options.get("debug");
  |     if (debugValue != null) {
  |       debug = "true".equalsIgnoreCase(debugValue);
  |     }
  | 
  |     query = (String)this.options.get("user_query");
  |     if (query == null) {
  |       throw new IllegalArgumentException(NULL_QUERY);
  |     }
  | 
  |     if (debug) {
  |       Debugger.println("Initialized login module");
  |     }
  |   }
  | 
  |   /*
  |    * (non-Javadoc)
  |    * 
  |    * @see javax.security.auth.spi.LoginModule#login()
  |    */
  |   @SuppressWarnings("unchecked")
  |   @Override
  |   public boolean login() throws LoginException {
  |     String name = null;
  |     StringBuilder pw = null;
  |     
  |     // initialize loginOk to false in superclass
  |     loginOk = false;
  | 
  |     // Set up the callbacks.
  |     Callback[] callbacks = new Callback[2];
  |     NameCallback nameCallback = new NameCallback("Name");
  |     PasswordCallback pwCallback = new PasswordCallback("Password", false);
  |     callbacks[0] = nameCallback;
  |     callbacks[1] = pwCallback;
  | 
  |     // Get the username and password from the callback handler.
  |     try {
  |       callbackHandler.handle(callbacks);
  |       name = nameCallback.getName();
  |       char[] tempPw = pwCallback.getPassword();
  |       if (tempPw == null) {
  |         // Use an empty array rather than a null.
  |         tempPw = new char[0];
  |       }
  |       // Copy the password array into a local StringBuilder and clear the
  |       // original.
  |       pw = new StringBuilder(new String(tempPw));
  |       pwCallback.clearPassword();
  | 
  |       // Look up the user in the database using the configured query.
  |       user = QueryUser.getUser(name, pw.toString(), query);
  | 
  |       // Clear the password from memory as a security measure.
  |       pw.delete(0, pw.length());
  | 
  |       if (user != null) {
  |         // Validate the user.
  |         loginOk = validateUser(user.getStatus());
  |         if (debug && loginOk) {
  |           Debugger.println("Logged in user " + name + " (" + user.getPersonId()
  |                            + ")");
  |         } else if (debug && !loginOk) {
  |           Debugger.println("Failed to log in user " + name);
  |         }
  |       }
  |     } catch (IOException e) {
  |       throw new LoginException(e.getMessage() + ": "
  |                                + e.getCause().getMessage());
  |     } catch (UnsupportedCallbackException e) {
  |       throw new LoginException(e.getMessage());
  |     } catch (CommunityException e) {
  |       throw new LoginException(e.getMessage());
  |     } finally {
  |       if (!loginOk) {
  |         // If validation failed, null out the user and throw login exception.
  |         user = null;
  |         throw new LoginException(FAILURE);
  |       }
  |     }
  | 
  |     return loginOk;
  |   }
  | 
  |   /**
  |    * Validate the user given the user's status. If the status is null, the user
  |    * is not valid. If the status is ACTIVE, the user is valid. If the status is
  |    * NEW, the user is not valid but the method queues a special message
  |    * reporting the status.
  |    * 
  |    * @param status the current status of the user, if any
  |    * @return true if the user is active and valid, false otherwise
  |    */
  |   private boolean validateUser(String status) {
  |     boolean validated = false;
  |     // The user must be an active user to be valid.
  |     if (status != null && status.equals(DataConstants.getActiveStatus())) {
  |       validated = true;
  |     } else if (status != null && status.equals(DataConstants.getNewStatus())) {
  |       FacesContext context = FacesContext.getCurrentInstance();
  |       // Queue global user-not-yet-active message, no component id
  |       String msg = WARN_PREFIX + ": " + NOT_YET_ACTIVE;
  |       FacesMessage fMsg = new FacesMessage(FacesMessage.SEVERITY_WARN, msg, "");
  |       context.addMessage(null, fMsg);
  |     } else {
  |       FacesContext context = FacesContext.getCurrentInstance();
  |       // Queue global invalid-user message, no component id
  |       String msg = ERROR_PREFIX + ": " + INVALID_USER;
  |       FacesMessage fMsg =
  |           new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, "");
  |       context.addMessage(null, fMsg);
  |     }
  |     return validated;
  |   }
  | 
  |   /**
  |    * Get the user login information.
  |    * 
  |    * @return a User object containing the login information for the user
  |    */
  |   public User getUser() {
  |     return user;
  |   }
  | 
  |   /*
  |    * (non-Javadoc)
  |    * 
  |    * @see org.jboss.security.auth.spi.AbstractServerLoginModule#getIdentity()
  |    */
  |   @Override
  |   protected Principal getIdentity() {
  |     if (debug) {
  |       Debugger.println("Getting identity for user " + user.getUsername() + " ("
  |                        + user.getPersonId() + ")");
  |     }
  |     return new SimplePrincipal(user.getUsername());
  |   }
  | 
  |   /*
  |    * (non-Javadoc)
  |    * 
  |    * @see org.jboss.security.auth.spi.AbstractServerLoginModule#getRoleSets()
  |    */
  |   @Override
  |   protected Group[] getRoleSets() throws LoginException {
  |     if (debug) {
  |       Debugger.println("Getting groups for user " + user.getUsername() + " ("
  |                        + user.getPersonId() + ")");
  |     }
  |     Group[] groups = { new SimpleGroup("Roles"), new SimpleGroup("CallerPrincipal") };
  | 
  |     // All users get the Community User role.
  |     groups[0].addMember(new SimplePrincipal(DataConstants.COMMUNITY_USER));
  |     if (debug) {
  |       Debugger.println(user.getUsername() + " gets Tair Community User role ");
  |     }
  | 
  |     // Tair and External Curator roles are defined in the database.
  |     if (user.isTairCurator()) {
  |       groups[0].addMember(new SimplePrincipal(DataConstants.TAIR_CURATOR));
  |       if (debug) {
  |         Debugger.println(user.getUsername() + " gets Tair Curator role ");
  |       }
  |     }
  |     if (user.isExternalCurator()) {
  |       groups[0].addMember(new SimplePrincipal(DataConstants.EXTERNAL_CURATOR));
  |       if (debug) {
  |         Debugger.println(user.getUsername() + " gets External Curator role");
  |       }
  |     }
  |     
  |     // Set the CallerPrincipal group Principal; JBoss uses this to respond to
  |     // the HTTPServletRequest.getUserPrincipal() method.
  |     groups[1].addMember(new SimplePrincipal(user.getUsername()));
  |     if (debug) {
  |       Debugger.println(user.getUsername() + " set as CallerPrincipal");
  |     }
  | 
  |     return groups;
  |   }
  | }
  | 

View the original post : http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4170867#4170867

Reply to the post : http://www.jboss.com/index.html?module=bb&op=posting&mode=reply&p=4170867



More information about the jboss-user mailing list