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というのがログイン構成ファイルで指定してあるので、そこに以下のエントリを追加する。
    1. cas {  
    2.   org.jasig.cas.client.jaas.CasLoginModule required  
    3.   ticketValidatorClass="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"  
    4.   casServerUrlPrefix="http://localhost/cas-server"  
    5.   defaultRoles="";  
    6. };  
    詳しくは以下の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. フィルターを作成する
    1. /** 
    2.  * 
    3.  */  
    4. package test;  
    5.   
    6. import java.io.IOException;  
    7. import java.security.GeneralSecurityException;  
    8. import java.security.Principal;  
    9.   
    10. import javax.security.auth.Subject;  
    11. import javax.security.auth.login.LoginContext;  
    12. import javax.servlet.FilterChain;  
    13. import javax.servlet.ServletException;  
    14. import javax.servlet.ServletRequest;  
    15. import javax.servlet.ServletResponse;  
    16. import javax.servlet.http.HttpServletRequest;  
    17. import javax.servlet.http.HttpServletRequestWrapper;  
    18. import javax.servlet.http.HttpServletResponse;  
    19. import javax.servlet.http.HttpSession;  
    20.   
    21. import org.jasig.cas.client.jaas.AssertionPrincipal;  
    22. import org.jasig.cas.client.jaas.ServiceAndTicketCallbackHandler;  
    23. import org.jasig.cas.client.util.AbstractCasFilter;  
    24. import org.jasig.cas.client.util.CommonUtils;  
    25. import org.jasig.cas.client.validation.Assertion;  
    26.   
    27. /** 
    28.  * @author hisato 
    29.  *  
    30.  */  
    31. public final class WebAuthenticationFilter extends AbstractCasFilter {  
    32.   
    33.  public class WebAuthenticationRequestWrapper extends  
    34.    HttpServletRequestWrapper {  
    35.   
    36.   private Principal principal = null;  
    37.   
    38.   /** 
    39.    * @param request 
    40.    */  
    41.   public WebAuthenticationRequestWrapper(HttpServletRequest request,  
    42.     Principal principal) {  
    43.    super(request);  
    44.    this.principal = principal;  
    45.   }  
    46.   
    47.   /* 
    48.    * (non-Javadoc) 
    49.    * @see javax.servlet.http.HttpServletRequestWrapper#getUserPrincipal() 
    50.    */  
    51.   @Override  
    52.   public Principal getUserPrincipal() {  
    53.    return this.principal;  
    54.   }  
    55.  }  
    56.   
    57.  public static final String CONST_CAS_LAST_TICKET = "_const_cas_last_ticket_";  
    58.   
    59.  public static final String CONST_CAS_LOGIN_SUBJECT = "_const_cas_login_subject_";  
    60.   
    61.  /* 
    62.   * (non-Javadoc) 
    63.   * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, 
    64.   * javax.servlet.ServletResponse, javax.servlet.FilterChain) 
    65.   */  
    66.  @Override  
    67.  public void doFilter(ServletRequest servletRequest,  
    68.    ServletResponse servletResponse, FilterChain chain)  
    69.    throws IOException, ServletException {  
    70.   HttpServletRequest request = (HttpServletRequest) servletRequest;  
    71.   HttpServletResponse response = (HttpServletResponse) servletResponse;  
    72.   HttpSession session = request.getSession();  
    73.   if (session != null) {  
    74.    String ticket = CommonUtils.safeGetParameter(request,  
    75.      getArtifactParameterName());  
    76.    Assertion assertion = (Assertion) session  
    77.      .getAttribute(CONST_CAS_ASSERTION);  
    78.    AssertionPrincipal principal = null;  
    79.    if (assertion != null) {  
    80.     request = new WebAuthenticationRequestWrapper(request,  
    81.       assertion.getPrincipal());  
    82.    }  
    83.    if (ticket != null  
    84.      && !ticket.equals(session  
    85.        .getAttribute(CONST_CAS_LAST_TICKET))) {  
    86.     try {  
    87.      String service = constructServiceUrl(request, response);  
    88.      LoginContext lc = new LoginContext(  
    89.        "cas",  
    90.        new ServiceAndTicketCallbackHandler(service, ticket));  
    91.      lc.login();  
    92.      Subject subject = lc.getSubject();  
    93.      for (Principal p : subject.getPrincipals()) {  
    94.       if (p instanceof AssertionPrincipal) {  
    95.        principal = (AssertionPrincipal) p;  
    96.        break;  
    97.       }  
    98.      }  
    99.      if (principal != null) {  
    100.       session.setAttribute(CONST_CAS_LOGIN_SUBJECT,  
    101.         subject);  
    102.       session.setAttribute(CONST_CAS_ASSERTION,  
    103.         principal.getAssertion());  
    104.       session.setAttribute(CONST_CAS_LAST_TICKET, ticket);  
    105.       request = new WebAuthenticationRequestWrapper(request,  
    106.         principal);  
    107.      } else {  
    108.       throw new GeneralSecurityException(  
    109.         "Web authentication did not produce CAS AssertionPrincipal.");  
    110.      }  
    111.     } catch (GeneralSecurityException e) {  
    112.      response.sendError(HttpServletResponse.SC_FORBIDDEN,  
    113.        e.getMessage());  
    114.     }  
    115.    }  
    116.    if (request.getUserPrincipal() == null) {  
    117.     session.removeAttribute(CONST_CAS_LOGIN_SUBJECT);  
    118.     session.removeAttribute(CONST_CAS_ASSERTION);  
    119.     session.removeAttribute(CONST_CAS_LAST_TICKET);  
    120.    }  
    121.   }  
    122.   chain.doFilter(request, response);  
    123.  }  
    124. }  
  5. アプリケーションにフィルターを設定する

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

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

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

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

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

0 件のコメント:

コメントを投稿