需求描述1:大家知道J2EE应用程序都可以用类型以下形式进行保护:
- <login-config>
- <auth-method>FORM</auth-method>
- <form-login-config>
- <form-login-page>/login.jsp</form-login-page>
- <form-error-page>/failure.jsp</form-error-page>
- </form-login-config>
- <realm-name>Security Realm</realm-name>
- </login-config>
- <security-constraint>
- <display-name>Tomcat security constraint</display-name>
- <web-resource-collection>
- <web-resource-name>Protected Resources</web-resource-name>
- <url-pattern>/security/*</url-pattern>
- </web-resource-collection>
- <auth-constraint>
- <role-name>manager</role-name>
- </auth-constraint>
- </security-constraint>
- <security-role>
- <role-name>manager</role-name>
- </security-role>
而使用CAS实现SSO之后,我们又不想容器保护去掉,该怎么办才能真正把这个CAS验证过的用户和角色传给Tomcat容器,而不至于重新登录,或者出现403错误呢?
需求描述2: 在有EJB资源的时候,我们通常都会用JAAS来实现保护EJB资源,以限制EJB资源的访问。在调用EJB的时候,需要输入用户名和密码并且此用户具有相应的角色才能调用EJB。而在实现SSO之后,如果真正实现与EJB容器进行SSO呢?
下面基于Tomcat容器实现需求1.
1. Tomcat窗口提供了一个叫Valve的接口,还提供一个基本实现ValveBase,我们解决方案就是基于实现一个自己的Valve, 在Valve里把经过CAS验证的用户传给Tomcat。以下就是自定义的Valve:
- package edu.extcas.valve;
- import java.io.*;
- import java.security.Principal;
- import java.util.ArrayList;
- import java.util.List;
- import javax.servlet.*;
- import javax.servlet.http.*;
- import org.apache.catalina.Container;
- import org.apache.catalina.connector.Request;
- import org.apache.catalina.connector.Response;
- import org.apache.catalina.realm.GenericPrincipal;
- import org.apache.catalina.valves.ValveBase;
- //import com.ttg.customagent.jboss.SimplePrincipal;
- public class ExtCASValve extends ValveBase {
- public final static String CAS_FILTER_USER = "edu.yale.its.tp.cas.client.filter.user";
- public void setContainer(Container container) {
- super.setContainer(container);
- }
- public void invoke(Request request, Response response) throws IOException,
- ServletException {
- HttpSession session = ((HttpServletRequest) request).getSession();
- if (session != null) {
- String username = (String) session.getAttribute(CAS_FILTER_USER);
- if (null != username) {
- List roleList = getRolesFromUserStore(username);
- Principal principal = new GenericPrincipal(request.getContext()
- .getRealm(), username, "", roleList);
- request.setUserPrincipal(principal);
- }
- //SecurityAssociation类是在登录EJB的时候使用的。
- //SecurityAssociation.setPrincipal(new SimplePrincipal(username.trim()));
- //SecurityAssociation.setCredential("password".trim().toCharArray());
- getNext().invoke(request, response);
- return;
- } else {
- getNext().invoke(request, response);
- return;
- }
- }
- //此方法是为在用户存储里取到相应的角色。自已根据实际情况实现
- private List getRolesFromUserStore(String username) {
- List roleList = new ArrayList();
- roleList.add("admin");
- roleList.add("manager");
- return roleList;
- }
- }
2. 实现好自己的Valve,在server.xml里配置一下。然后就可以测试了。
下面讲实现需求2实现的思路:
1. 如果是EJB容器是JBoss,而Web容器是Tomcat, 也可以在Valve里用SecurityAssociation类(或者其他的方法)把CAS验证过的用户传到EJB容器里。这里同时需要传递密码。
2. 要实现一个LoginModule, 类似以下代码:
- package edu.extcas.loginmodule;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.security.Principal;
- import java.security.cert.X509Certificate;
- import javax.security.auth.Subject;
- import javax.security.auth.callback.Callback;
- import javax.security.auth.callback.CallbackHandler;
- import javax.security.auth.callback.NameCallback;
- import javax.security.auth.callback.PasswordCallback;
- import javax.security.auth.callback.UnsupportedCallbackException;
- import javax.security.auth.login.LoginException;
- import javax.security.auth.spi.LoginModule;
- import org.apache.catalina.realm.GenericPrincipal;
- public class ExtCasLoginModule implements LoginModule {
- private CallbackHandler callbackHandler;
- private Principal unauthenticatedIdentity;
- private String userName = null;
- private String password = null;
- protected Subject subject;
- protected boolean loginOk;
- protected Principal identity;