这阵子在研究yale大学的jasig项目下面的CAS单点登陆系统(项目地址是:http://www.jasig.org/cas),经过一周的测试,整个安装部署都已经成功了,现在将部署过程记录如下:
一、准备工作:
装好JDK(1.6),Tomcat(6.0),下载CAS服务端和客户端(cas-server-3.4.11-release和cas-client-3.2.1-release,这两个版本是目前最新的,下载地址是:http://www.jasig.org/cas_server_3_4_11_release)。
二、设置证书:
生成服务端的证书,这里和其它教程一样采用的都是JDK所自带的keytool工具:
1).生成证书(server端):
keytool -genkey -alias mycas -keyalg RSA -keystore d:\keys\mycas.keystore;
2.导出证书(server端):
keytool -export -trustcacerts -file d:\keys\mycas.crt -alias mycas -keystore d:\keys\mycas.keystore
3.查看证书(client端,即存放子应用的机器,我这里以windows演示):
(首先进入JDK目录 cd C:\Program Files\Java\jdk1.6.0_10\jre\lib\security) keytool -list -keystore cacerts
4.导入证书(client端,为方便演示,我在client的D盘下也建了这个目录,然后把crt文件放进去了):
keytool -import -trustcacerts -keystore cacerts -file d:\keys\mycas.crt -alias mycas -storepass changeit
5.删除证书(client端,不是必须的操作,这里只是列出来,需要删除的时候才用的):
keytool -delete -trustcacerts -alias mycas -keystore cacerts -storepass changeit
三、开启服务端Tomcat的SSL连接服务,确保Tomcat所用的JDK和之前所装的JDK是相同的(有的eclipse中的tomcat是用的eclipse自带的jdk的,这个要区分好的)。
修改Tomcat的conf下面server.xml文件,去掉以下代码的注释,然后添加你自己的keystore.
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="d:\keys\mycas.keystore"
keystorePass="你的密码"
/>
四、设置服务端配置文件。首先将cas-server-3.4.11解压后,复制modules下面的cas-server-webapp-3.4.11.war到tomcat的webapp下面(手动解压或者启动tomcat解压)。启动tomcat后,就可以在地址栏,以https://wangyq(我自己的计算机名):8443/cas/ 来进行访问了.
cas的配置文件在deployerConfigContext.xml中默认的登陆验证方式是SimpleTestUsernamePasswordAuthenticationHandler,也就是用户名和密码一致就可以登陆;我们通常需要从数据库中取出用户名和密码进行验证,所以我们需要修改deployerConfigContext.xml,配置我们自己的服务认证方式:首先在deployerConfigContext.xml配置我们数据源,在<sec:user-service id="userDetailsService">这个节点后面添加casDataSource:
<bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>oracle.jdbc.driver.OracleDriver</value> </property> <property name="url"> <value>jdbc:oracle:thin:@10.0.12.67:1521:orcl</value> </property> <property name="username"> <value>username</value> </property> <property name="password"> <value>yourpassword</value> </property> </bean>
然后注释掉SimpleTestUsernamePasswordAuthenticationHandler,添加QueryDatabaseAuthenticationHandler:
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="casDataSource" />
<property name="sql" value="select password from userinformation where username = ?" />
</bean>
说明:默认的它是不带数据库的驱动的,要自己把相应的数据库驱动程序放到cas的lib下面,我这里需要的ojdbc.jar.
五、配置客户端, 简单点就是把官方提供的客户端的jar加入到第三方应用的项目中,然后在第三方应用的web.xml中配置好对应的过滤器即可了。我这里把我的cas-client-core-3.2.1.jar这个包直接加入到我的第三方应用的lib下面了,然后在web.xml中加上过滤器的配置:
<!-- ======================== 单点登录开始 ======================== --> <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置--> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 该过滤器用于实现单点登出功能,可选配置。 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责用户的认证工作,必须启用它;serverName中填写第三方应用所在服务器的连接地址,注意不要出现localhost或者127.0.0.1,不然会出现无法同步登出的问题 --> <filter> <filter-name>CASFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://wangyq:8443/cas/login</param-value> <!--这里的server是服务端的IP--> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://192.168.5.100:8080</param-value> </init-param> </filter> <filter-mapping> <filter-name>CASFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责对Ticket的校验工作,必须启用它;serverName中填写第三方应用所在服务器的连接地址,注意不要出现localhost或者127.0.0.1,不然会出现无法同步登出的问题 --> <filter> <filter-name>CAS Validation Filter</filter-name> <filter-class> org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>https://wangyq:8443/cas</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://192.168.5.100:8080</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 --> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class> org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 --> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 自动根据单点登录的结果设置本系统的用户信息 --> <filter> <display-name>AutoSetUserAdapterFilter</display-name> <filter-name>AutoSetUserAdapterFilter</filter-name> <filter-class>cn.util.AutoSetUserAdapterFilter</filter-class> </filter> <filter-mapping> <filter-name>AutoSetUserAdapterFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- ======================== 单点登录结束 ======================== -->
然后附上AutoSetUserAdapterFilter这个过滤器的代码:
package cn.util;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.jasig.cas.client.validation.Assertion;
import cn.domain.UserInformation;
/**
* CAS单点登陆的过滤器功能类,该类用来自动生成子应用的登陆Session
*
*/
public class AutoSetUserAdapterFilter implements Filter {
/**
* Default constructor.
*/
public AutoSetUserAdapterFilter() {
}
/**
* @see Filter#destroy()
*/
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// _const_cas_assertion_是CAS中存放登录用户名的session标志
Object object = httpRequest.getSession().getAttribute("_const_cas_assertion_");
if (object != null) {
Assertion assertion = (Assertion) object;
String loginName = assertion.getPrincipal().getName();
UserInformation user = UserUtil.getCurrentUser(httpRequest);
// 第一次登录系统
if (user == null) {
user = cn.util.CommomMethod.getUserBean(loginName);
// 保存用户信息到Session
UserUtil.saveUserToSession(user,httpRequest);
}
}
chain.doFilter(request, response);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
}
}
UserUtil就是一个简单的操作用户登录session的一个工具类,代码如下:
package cn.util;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import cn.domain.UserInformation;
/**
* 该类被用来保存、获取、删除用户的SessionBean信息
* @author WangYQ
*
*/
public class UserUtil{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 用户的Session标志
*/
public static String USER = "ui";
/**
* 已登录的用户
*/
public static Map<String, UserInformation> loginUsers = new HashMap<String, UserInformation>();
/**
* 保存用户信息到Session
* @param user
*/
public static void saveUserToSession(UserInformation user,HttpServletRequest httpRequest) {
httpRequest.getSession().setAttribute("ui", user);
}
/**
* 获取当前登录的用户
* @return
*/
public static UserInformation getCurrentUser(HttpServletRequest httpRequest) {
HttpSession session = httpRequest.getSession();
if(session!=null){
Object sessionUser = session.getAttribute("ui");
if(sessionUser == null){
return null;
}
UserInformation user = (UserInformation) sessionUser;
return user;
}else{
return null;
}
}
/**
* 获取当前用户的帐号
* @return 当前用户的帐号
*/
public static String getCurrentUserName(HttpServletRequest httpRequest) {
Object sessionUser = getCurrentUser(httpRequest);
if(sessionUser==null){
return "";
}else{
UserInformation user = (UserInformation) sessionUser;
return user.getUsername();
}
}
/**
* 从session中移除用户
*/
public static void removeUserFromSession(HttpServletRequest httpRequest) {
httpRequest.getSession().removeAttribute("ui");
}
}
CommomMethod.getUserBean这也是一个工具类中根据用户帐号获取用户信息bean的一个方法,为了演示我简化下代码:
package cn.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.util.ServletContextAware;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import com.opensymphony.xwork2.ActionContext;
import cn.domain.UserInformation;
import cn.service.SessionService;
import cn.domain.UserInformation;
public class CommomMethod {
private static JdbcTemplate template ;
//其它的业务代码省略
public static UserInformation getUserBean(String username){
UserInformation UI = new UserInformation();
int type = 1;
String uname="",upass="";
SqlRowSet rs =null;
template = HibernateSessionFactory.gettemplate();
String hsql = "select username,password,type,realname,roletype,schoolname,school_id,role_name,avatarurl,isupdate from userinformation where username='"+username+"'";
rs = template.queryForRowSet(hsql);
while(rs.next()){
uname = rs.getString(1);
upass = rs.getString(2);
type = rs.getInt(3);
UI.setUsername(uname);
UI.setPassword(upass);
UI.setType(type);
UI.setRealname(rs.getString("realname"));
UI.setRoletype(rs.getInt("roletype"));
UI.setSchoolname(rs.getString("schoolname"));
UI.setSchool_id(rs.getInt("school_id"));
UI.setRolename(rs.getString("role_name"));
UI.setAvatarurl(rs.getString("avatarurl"));
UI.setUpdate(rs.getString("isupdate").equals("1"));
}
return UI;
}
}
UserInformation 就是一个简单的javabean存放的用户的一些基本属性,我这里是直接使用的jdbctemplate,如果你使用的是hibernate的话,可以自行变更获取方法;
六、最后一步,就是把由服务端生成的证书导入到第三方应用所在服务器的JDK的受信任的证书列表中,详细的可以看第二步骤中的第四小点,需要说明的是,要在cmd中首先进入到jdk的目录中(cd C:\Program Files\Java\jdk1.6.0_10\jre\lib\security),然后再导入操作。那至此全部操作就已经完成了,导入证书完成后,分别启动服务端cas和客户端的应用,就能实现自己的单点登陆和单点登出了。
总结一点:CAS的单点登陆安装和配置我已经成功完成了,目前对代码的研究还没开始,只是处在一个逻辑流程的熟悉阶段,感觉其实最主要的工作还是在客户端,通过过滤器把所有的对于第三方应用的未登录访问全部指向到cas的server进行验证,验证成功后再把地址redirect到先前的service上面。
写这篇文章,参考了很多别人的东西,这里推荐两个教程:
http://www.wsria.com/archives/1349(咖啡兔,写的很详细,我开始也是参照着来做的)
http://wenku.baidu.com/view/a2352819227916888486d7e8.html(百度文库,一些细节写的很详细)
最后新年第一天报道,祝大家龙年龙马精神,步步高升,呵呵~