CSS

Friday, October 12, 2012

CAS Single Sign On Server Seam 3 Security Integration

Where I work at we use the CAS Single Signon Server so that our users only have to sign into one of our Web applications. They are then signed into all of our applications. This makes integration between them very easy. For my newest application We decided to use JSF and Seam Security.

Holder for CAS Returned Information

The following class is used to hold the information returned in the CAS Assertion. I have pulled out the getters, setters, equals and toString.

package org.yfu.util.cas;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.lang.StringUtils;
import org.jasig.cas.client.validation.Assertion;
import org.jboss.solder.logging.Logger;
import org.picketlink.idm.api.User;

public class CASUserBean implements Serializable, User 
{
 
 private static final long serialVersionUID = 401263788421715826L;
 
 @Inject
 private Logger log;
 
 
 private static final String STUDENT = "student";
// private static final String HOST_FAMILY = "hostfamily";
// private static final String PARTNER = "partner";
 private static final String VOLUNTEER = "volunteer";
 private static final String STAFF = "staff";
 private static final String REWARDS = "Rewards";
 private static final String TELEPHONE_NUMBER = "telephoneNumber";
 private static final String DEPARTMENT = "department";
 private static final String MAIL = "mail";
 private static final String TITLE = "title";
 private static final String ELECTRONIC_SIGNATURE = "electronicSignature";
 private static final String USER_LEVEL = "userLevel";
 private static final String POSTAL_CODE = "postalCode";
 private static final String SECURITY_QUESTION_ANSWER = "securityQuestionAnswer";
 private static final String SECURITY_QUESTION = "securityQuestion";
 private static final String PFO = "pfo";
 private static final String DISTINGUISHED_NAME = "distinguishedName";
 private static final String USER_PRINCIPAL_NAME = "userPrincipalName";
 private static final String NAME = "Name";


 private Integer pfoId;
 private String username;
 private String name;
 private List<String> groups = new ArrayList<String>();;
 private String email;
 private String securityQuestion;
 private String securityQuestionAnswer;
 private String postalCode;
 private String electronicSignature;
 private String title;
 private String studentID;
 private String department;
 private String telephone;
 private String userLevel;
 private Date validUntilDate; 
 private Date validFromDate;
 private String distinguishedName;
 private boolean loggedIn;
 private boolean staff;
 private boolean volunteer;
 
 
 public CASUserBean() {

 }
 
 public void init(Assertion assertion) {
  if (assertion != null && assertion.getPrincipal() != null) {
   this.validFromDate = assertion.getValidFromDate(); 
   this.validUntilDate = assertion.getValidUntilDate();
   Map<String, Object> attribs = assertion.getPrincipal().getAttributes();
   this.department = (String) attribs.get(DEPARTMENT);
   this.distinguishedName = (String) attribs.get(DISTINGUISHED_NAME);
   this.electronicSignature = (String) attribs.get(ELECTRONIC_SIGNATURE);
   this.email = (String) attribs.get(MAIL);
   this.name = (String) attribs.get(NAME);
   this.postalCode = (String) attribs.get(POSTAL_CODE);
   this.securityQuestion = (String) attribs.get(SECURITY_QUESTION);
   this.securityQuestionAnswer = (String) attribs.get(SECURITY_QUESTION_ANSWER);
   this.studentID = (String) attribs.get(STUDENT);
   this.telephone = (String) attribs.get(TELEPHONE_NUMBER);
   this.title = (String) attribs.get(TITLE);
   this.userLevel = (String) attribs.get(USER_LEVEL);
   this.username = (String) attribs.get(USER_PRINCIPAL_NAME);
   this.setLoggedIn(true);
   
   String value = (String) attribs.get(PFO);
   
   if (org.apache.commons.lang.StringUtils.isNotBlank(value)
     && org.apache.commons.lang.StringUtils.isNumeric(value)) {
    this.pfoId = Integer.valueOf(value);
   }
   
      
   groups = new ArrayList<String>();
   Object ldapGroups = attribs.get("group");
   
   if (ldapGroups instanceof List) {
    List<?> list = (List<?>) ldapGroups;
       for (Object obj : list ) {
        String group = (String) obj;
        addRole(group);
    }
   } else {
    addRole((String) ldapGroups);
   }
   
      
      value =  getDistinguishedName();
      
      if (StringUtils.contains(value, "DC=YFUUSA,DC=Local")) {
       groups.add(STAFF);
      }
      
      setStaff(getGroups().contains(STAFF));
      setVolunteer(getGroups().contains(VOLUNTEER));
  }
 }

 private void addRole(String group) {
  log.trace(group);
  group = StringUtils.substringAfter(group, "CN=");
  log.trace(group);
  group = StringUtils.substringBefore(group, ",");
  log.trace(group);
  if (StringUtils.isNotBlank(group)) {
   groups.add(group);
  }
 }

 @Override
 public String getKey() {
  return getDistinguishedName();
 }

 @Override
 public String getId() {
  return "" + getPfoId();
 }
}

Producer For CASUserBean

Nothing much interesting here.  Just a simple producer.  You could get away with out this I have it since I started not using Seam Security.

package org.yfu.util.cas;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.New;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;

import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.jboss.solder.servlet.http.HttpSessionStatus;

@RequestScoped @Alternative 
public class CASUserProducer {

 
 @Inject private HttpSessionStatus sessionStatus;
 
 public CASUserProducer() {
  
 }
 
 @Produces
 @Named("casUser")
 @SessionScoped
 public CASUserBean getCasUser(@New CASUserBean user) {
  CASUserBean ret = user;
  
  ret.setName("Not Logged in");
  
  if (sessionStatus.isActive()) {
   HttpSession session = sessionStatus.get();
   Assertion assertion = (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
   if (assertion != null && assertion.getPrincipal() != null) {
    user.init(assertion);
    ret = user;
   }
  } 
  return ret;
 }

}

Seam Security Authenticator

The need this little class so that we can tell Seam Security we are logged in.  We also set the CASUserBean as our User.
package org.yfu.util.cas;

import javax.inject.Inject;
import javax.inject.Named;

import org.jboss.seam.security.BaseAuthenticator;
import org.jboss.seam.security.Identity;

public class CASSeamAuthenticator extends BaseAuthenticator
{

 
 @Inject  @Named("casUser")  private CASUserBean casUser;
 
 public CASSeamAuthenticator() {
 }

 @Override
 public void authenticate() {
  if (casUser != null && casUser.isLoggedIn()) {
   setStatus(AuthenticationStatus.SUCCESS);
   setUser(casUser);   
  }  

 }

 @Override
 public void postAuthenticate() {

 }


}


A Servlet Filter

We now create a Servlet filter that will be placed after our CAS Servlet Filters.  This is where we actually do the login (see line 38).
package org.jasig.cas.client.seam3.authentication;

import java.io.IOException;

import javax.inject.Inject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.jboss.seam.security.Identity;
import org.yfu.util.cas.CASUserBean;


public class Seam3SecurityAuthenticationFilter implements Filter {


 @Inject private Identity identity;
 
 
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
   FilterChain chain) throws IOException, ServletException {

        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : null;

        if (!identity.isLoggedIn() && assertion != null) {
         identity.login();  // this is alway successful
         
         CASUserBean user = (CASUserBean) identity.getUser();
         
         // Now we will add LDAP Groups as Groups with a group type of group.  
         
         for (String group : user.getGroups()) {
    identity.addGroup(group, "group");
   }
         
         
         
        }
         
        
        chain.doFilter(request, response);
  
 }


 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
  
 }


 @Override
 public void destroy() {
  
 }

 
}

Include the filter in the web.xml

I included the CAS filters so that you can see them in order. The order of the filter mappings is vitally important.

 
  CASWebAuthenticationFilter
  org.jasig.cas.client.jboss.authentication.WebAuthenticationFilter7
  
   casServerLoginUrl
   https://login.yfu.org/cas/login
  
  
   serverName
   ${cas.serverName}
  
 
 
  CASAuthenticationFilter
  org.jasig.cas.client.authentication.AuthenticationFilter
  
   casServerLoginUrl
   https://login.yfu.org/cas/login
  
  
   serverName
   ${cas.serverName}
  
 
 
  CASSeam3SecurityFilter
  org.jasig.cas.client.seam3.authentication.Seam3SecurityAuthenticationFilter
 
 
 
 
  CASWebAuthenticationFilter
  /secured/*
 
 
  CASAuthenticationFilter
  /secured/*
 
 
  CASSeam3SecurityFilter
  /secured/*