CASのJAASモジュールであるCasLoginModuleをGlassFishで動かしてみました。
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
これを${com.sun.aas.instanceRoot}/lib/extとかにおいておく。
- ログイン構成ファイルに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="";
- };
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;
-
-
-
-
-
- public final class WebAuthenticationFilter extends AbstractCasFilter {
-
- public class WebAuthenticationRequestWrapper extends
- HttpServletRequestWrapper {
-
- private Principal principal = null;
-
-
-
-
- public WebAuthenticationRequestWrapper(HttpServletRequest request,
- Principal principal) {
- super(request);
- this.principal = principal;
- }
-
-
-
-
-
- @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_";
-
-
-
-
-
-
- @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);
- }
- }
/**
*
*/
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でメッセージ認証を実装した方がいい気がします。
そのうち作ってみます~。