CASのticketは1度しかバリデーションできない(あたりまえか)ので、セッションが利用できないrealmではオブジェクト(この場合、ticketなど)を保持できないので、全てのアクセスで同じticketを使いまわす→2回目以降のバリデーションでエラーになると思われる。
ProgrammaticLoginとか、HttpServletRequest#login(V3.0~)もrealmを利用するので、LoginContextで取得したSubjectを解析し、Assertionとかを取得して制御するようにしてみた。
JBossやTomcat用のintegrationモジュールも似たことやってるし、それを参考に作ってみた。
- CAS Clientのjarファイルをコンテナに配備
- cas-client-core-3.1.xx.jar
- commons-logging-1.1.jar
- ログイン構成ファイルにcasのLoginModuleを登録する デフォルトでは、${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を再起動します。
/** * */ 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); } }
クラスローダーは子を優先するようにしておく必要がありました。
1.をスキップして、casのjarファイルをアプリケーションに放り込んでおけば、クラスローダーの設定はどっちでも良いようです。
とりあえず、コンテナー依存のコードはないので、GlassFish以外でも多分動くと思われます。
でも、UserPrincipalと、デフォルトロールのマッピングぐらいしかできないです。
(配備記述子で設定したグループとロールのマッピングはやってくれるはずがないw)
まぁ認証(Authorization)だけならこれでもいいけど、承認(Authentication)をちゃんとやるなら、JSR-196でメッセージ認証を実装した方がいい気がします。
そのうち作ってみます~。
0 件のコメント:
コメントを投稿