SyntaxHighlighter

2011年1月17日月曜日

GlassFishでCasLoginModuleを動かしてみた

CASのJAASモジュールであるCasLoginModuleをGlassFishで動かしてみました。

CASのticketは1度しかバリデーションできない(あたりまえか)ので、セッションが利用できないrealmではオブジェクト(この場合、ticketなど)を保持できないので、全てのアクセスで同じticketを使いまわす→2回目以降のバリデーションでエラーになると思われる。
ProgrammaticLoginとか、HttpServletRequest#login(V3.0~)もrealmを利用するので、LoginContextで取得したSubjectを解析し、Assertionとかを取得して制御するようにしてみた。
JBossやTomcat用のintegrationモジュールも似たことやってるし、それを参考に作ってみた。

  1. CAS Clientのjarファイルをコンテナに配備
    • cas-client-core-3.1.xx.jar
    • commons-logging-1.1.jar
    これを${com.sun.aas.instanceRoot}/lib/extとかにおいておく。
  2. ログイン構成ファイルにcasのLoginModuleを登録する
  3. デフォルトでは、${com.sun.aas.instanceRoot}/config/login.confというのがログイン構成ファイルで指定してあるので、そこに以下のエントリを追加する。
    cas {
      org.jasig.cas.client.jaas.CasLoginModule required
      ticketValidatorClass="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"
      casServerUrlPrefix="http://localhost/cas-server"
      defaultRoles="";
    };
    詳しくは以下のjavadocに書いてあります。 http://www.jarvana.com/jarvana/view/org/jasig/cas/client/cas-client-core/3.1.11/cas-client-core-3.1.11-javadoc.jar!/org/jasig/cas/client/jaas/CasLoginModule.html ※defaultRolesはoptionalと書いてあるけど、何も指定していないとNPEになります。 上記の設定ができたら、GlassFishを再起動します。
  4. フィルターを作成する
  5. /**
     *
     */
    package test;
    
    import java.io.IOException;
    import java.security.GeneralSecurityException;
    import java.security.Principal;
    
    import javax.security.auth.Subject;
    import javax.security.auth.login.LoginContext;
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.jasig.cas.client.jaas.AssertionPrincipal;
    import org.jasig.cas.client.jaas.ServiceAndTicketCallbackHandler;
    import org.jasig.cas.client.util.AbstractCasFilter;
    import org.jasig.cas.client.util.CommonUtils;
    import org.jasig.cas.client.validation.Assertion;
    
    /**
     * @author hisato
     * 
     */
    public final class WebAuthenticationFilter extends AbstractCasFilter {
    
     public class WebAuthenticationRequestWrapper extends
       HttpServletRequestWrapper {
    
      private Principal principal = null;
    
      /**
       * @param request
       */
      public WebAuthenticationRequestWrapper(HttpServletRequest request,
        Principal principal) {
       super(request);
       this.principal = principal;
      }
    
      /*
       * (non-Javadoc)
       * @see javax.servlet.http.HttpServletRequestWrapper#getUserPrincipal()
       */
      @Override
      public Principal getUserPrincipal() {
       return this.principal;
      }
     }
    
     public static final String CONST_CAS_LAST_TICKET = "_const_cas_last_ticket_";
    
     public static final String CONST_CAS_LOGIN_SUBJECT = "_const_cas_login_subject_";
    
     /*
      * (non-Javadoc)
      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
      * javax.servlet.ServletResponse, javax.servlet.FilterChain)
      */
     @Override
     public void doFilter(ServletRequest servletRequest,
       ServletResponse servletResponse, FilterChain chain)
       throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest) servletRequest;
      HttpServletResponse response = (HttpServletResponse) servletResponse;
      HttpSession session = request.getSession();
      if (session != null) {
       String ticket = CommonUtils.safeGetParameter(request,
         getArtifactParameterName());
       Assertion assertion = (Assertion) session
         .getAttribute(CONST_CAS_ASSERTION);
       AssertionPrincipal principal = null;
       if (assertion != null) {
        request = new WebAuthenticationRequestWrapper(request,
          assertion.getPrincipal());
       }
       if (ticket != null
         && !ticket.equals(session
           .getAttribute(CONST_CAS_LAST_TICKET))) {
        try {
         String service = constructServiceUrl(request, response);
         LoginContext lc = new LoginContext(
           "cas",
           new ServiceAndTicketCallbackHandler(service, ticket));
         lc.login();
         Subject subject = lc.getSubject();
         for (Principal p : subject.getPrincipals()) {
          if (p instanceof AssertionPrincipal) {
           principal = (AssertionPrincipal) p;
           break;
          }
         }
         if (principal != null) {
          session.setAttribute(CONST_CAS_LOGIN_SUBJECT,
            subject);
          session.setAttribute(CONST_CAS_ASSERTION,
            principal.getAssertion());
          session.setAttribute(CONST_CAS_LAST_TICKET, ticket);
          request = new WebAuthenticationRequestWrapper(request,
            principal);
         } else {
          throw new GeneralSecurityException(
            "Web authentication did not produce CAS AssertionPrincipal.");
         }
        } catch (GeneralSecurityException e) {
         response.sendError(HttpServletResponse.SC_FORBIDDEN,
           e.getMessage());
        }
       }
       if (request.getUserPrincipal() == null) {
        session.removeAttribute(CONST_CAS_LOGIN_SUBJECT);
        session.removeAttribute(CONST_CAS_ASSERTION);
        session.removeAttribute(CONST_CAS_LAST_TICKET);
       }
      }
      chain.doFilter(request, response);
     }
    }
    
  6. アプリケーションにフィルターを設定する

クラスローダーは子を優先するようにしておく必要がありました。
1.をスキップして、casのjarファイルをアプリケーションに放り込んでおけば、クラスローダーの設定はどっちでも良いようです。

とりあえず、コンテナー依存のコードはないので、GlassFish以外でも多分動くと思われます。

でも、UserPrincipalと、デフォルトロールのマッピングぐらいしかできないです。
(配備記述子で設定したグループとロールのマッピングはやってくれるはずがないw)

まぁ認証(Authorization)だけならこれでもいいけど、承認(Authentication)をちゃんとやるなら、JSR-196でメッセージ認証を実装した方がいい気がします。

そのうち作ってみます~。

0 件のコメント:

コメントを投稿