CAS单点登陆部署

这阵子在研究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(百度文库,一些细节写的很详细)

最后新年第一天报道,祝大家龙年龙马精神,步步高升,呵呵~

猜你喜欢

转载自wang5598.iteye.com/blog/1358257
今日推荐