最近在研究sso单点登录的问题,研究了网上许多案例以及代码,发现总是有很大的差别,最后通过官网下载,才发现cas3 与 cas 4 的配置存在区别,这就是差别很大的原因了。

官网地址如下: https://www.apereo.org/search/node/cas


下面只针对cas4进行整理吧,因为cas4 相对于cas3 来说,是有增无减了。

1.下载

地址http://downloads.jasig.org/

cas-server-4.0.0-release.tar.gz

cas-client-3.3.3-release.tar.gz


2.配置Sever端

解压cas-server-4.0.0,将其中module/cas-server-webapp-4.0.0.war复制到Tomcat的webapps目录下,重命名为cas.war,启动Tomcat解开压缩

启动tomcat


http://localhost:8080/cas,进入登录页面。

默认用户为casuser/Mellon,登录成功即启动正常,cas3的无默认用户,只需用户名密码一直即可。


2.1设置利用数据库来验证用户


需依赖:c3p0-0.9.1.2.jar,mysql-connector-java-5.1.21.jar,cas-server-support-jdbc-4.0.0.jar,cas-server-support-ldap-4.0.0.jar

修改 deployerConfigContext.xml



1)更换验证方式 
  
   <!--
   <bean id="primaryAuthenticationHandler"
          class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
        <property name="users">
            <map>
                <entry key="casuser" value="Mellon"/>
            </map>
        </property>
    </bean>
    -->
    
    <!-- Define the DB Connection -->
    <bean id="dbAuthHandler" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"
      p:dataSource-ref="dataSource"
      p:sql="select passwd from users where name=?" />
      
      
     <!-- 使用dataSource-->
     <bean id="dataSource"
     class="com.mchange.v2.c3p0.ComboPooledDataSource"
     p:driverClass="com.mysql.jdbc.Driver"
     p:jdbcUrl="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=utf-8"
     p:user="root"
     p:password="123" />
2)更换handler验证方式
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
	<constructor-arg>
		<map>
			<entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
			 <entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver" />
			 <!--本来使用primaryAuthenticationHandler,现在使用dbAuthHandler -->
			<!-- <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" /> -->
		</map>
	</constructor-arg>

注释了原来的默认用户,使用数据库替代,新建数据库test,暂时没有使用md5加密,数据库test的users表直接设置passwd和name即可

3)使用md5加密,密码校验类自定义实现
 <bean id="dbAuthHandler" class="com.test.cas.MyQueryDatabaseAuthenticationHandler"
	  p:dataSource-ref="dataSource"
	  p:sql="select passwd from users where name=?"
	  p:passwordEncoder-ref="passwordEncoder"/>
	  
<bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"
      c:encodingAlgorithm="md5"
      p:characterEncoding="UTF-8" />	
4)密码校验类自定义实现

public class MyQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler{
	@NotNull
    private String sql;
	
	@Override
	protected HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential)throws GeneralSecurityException, PreventedException {
		 final String username = credential.getUsername();
		   
		  final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword()+"{"+username+"}");
	        try {
	            final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
	            if (!dbPassword.equals(encryptedPassword)) {
	                throw new FailedLoginException("Password does not match value on record.");
	            }
	        } catch (final IncorrectResultSizeDataAccessException e) {
	            if (e.getActualSize() == 0) {
	                throw new AccountNotFoundException(username + " not found with SQL query");
	            } else {
	                throw new FailedLoginException("Multiple records found for " + username);
	            }
	        } catch (final DataAccessException e) {
	            throw new PreventedException("SQL exception while executing query for " + username, e);
	        }
	        return createHandlerResult(credential, new SimplePrincipal(username), null);
	}
	
    public void setSql(final String sql) {
        this.sql = sql;
    }
}


因为这里的密码采用了   密码{用户名}  加密的方式,所以使用了自定义的密码校验,重启,测试通过


3.配置Client端

3.1 普通方式

新建testweb项目,在Client工程WEB-INF/lib下添加cas-client-core-3.2.1.jar包

修改web.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>testWeb1</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

<!-- ======================== 单点登录/登出 ======================== -->
 
 <!-- 定义serverName,一个全局变量 -->
    <context-param>
        <param-name>serverName</param-name>
        <param-value>http://localhost:8080</param-value>
    </context-param>
    
     
<!-- 该过滤器用于实现单点登出功能,可选配置。 -->
<filter>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>

<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
  <filter-name>CAS Authentication Filter</filter-name>
  <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
  <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>https://localhost:8080/cas/login</param-value>
  </init-param>
</filter>

<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>https://localhost:8080/cas</param-value>
    </init-param>
    <init-param>
        <param-name>redirectAfterValidation</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<!-- 该过滤器负责实现HttpServletRequest请求的包裹,
比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 -->
<filter>
  <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
  <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>

<!-- 该过滤器使得开发者可以通过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 Single Sign Out Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- ======================== 单点登录/登出结束 ======================== -->

     <servlet>
        <servlet-name>HelloWorldExample</servlet-name>
        <servlet-class>servlets.HelloWorld</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloWorldExample</servlet-name>
        <url-pattern>HelloWorldExample</url-pattern>
    </servlet-mapping>  

</web-app>


这里配置了HelloWorld servlet 来配合测试

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
        String username = principal.getName(); 
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!"+username+"</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

访问localhost:8080/testWeb1/HelloWorldExample  ,首次访问任一页面就会跳转到

https://localhost:8080/cas/login进行认证


3.2 spring 方式

这里采用spring security 与cas结合的方式

<!-- entry-point-ref入口点的意思,当捕捉到尚未登陆,尝试访问其他url时,会自动跳到入口点 -->
<http  auto-config="true" entry-point-ref="casAuthenticationEntryPoint" >
	<custom-filter ref="myFilter"   before="FILTER_SECURITY_INTERCEPTOR" />
	
	<!-- CasAuthenticationFilter总是监听/j_spring_cas_security_check请求 -->
	<!-- 认证请求将被配置的AuthenticationManager处理 -->
	<custom-filter position="CAS_FILTER" ref="casAuthenticationFilter" />

	<access-denied-handler ref="accessDeniedHandler"></access-denied-handler>
</http>

<beans:bean id="casAuthenticationEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
	<beans:property name="loginUrl" value="http://sso.test.com/login"></beans:property>
	<!-- service指定了回调url,当认证成功时,会跳回到这 -->
	<beans:property name="serviceProperties" ref="serviceProperties"></beans:property>
</beans:bean>

<!-- service指定了回调url,当认证成功时,会跳回到这 -->
<beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
	<beans:property name="service" value="http://localhost:8080/testweb/j_spring_cas_security_check"></beans:property>
	<beans:property name="sendRenew" value="false"></beans:property>
</beans:bean>

<!--当sso验证成功后,所有请求会在此处理 -->
<beans:bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
	<beans:property name="authenticationUserDetailsService" ref="authenticationUserDetailsService"/>
	<beans:property name="serviceProperties" ref="serviceProperties"></beans:property>
	<!-- 校验服务票据 -->
	<!-- Cas20TicketValidator 解析从CAS服务器收到的XML,它给CasAuthenticationProvider 返回一个TicketResponse,其中包含用户名和代理列表 -->
	<!-- 接下来将请求AuthenticationUserDetailsService 去加载应用于包含在Assertion中用户的GrantedAuthority 对象 -->
	<beans:property name="ticketValidator">
		<beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
		<!-- 配置cas服务器 -->
			<beans:constructor-arg index="0" value="http://sso.test.com/" />
		</beans:bean>
	</beans:property>
	<beans:property name="key" value="cas" />
</beans:bean>

	<!-- authorities对应 CAS server的 登录属性, 在此设置到spirng security中,用于spring security的验证 -->
	<beans:bean id="authenticationUserDetailsService" class="com.test.security.MyGrantedAuthorityFromAssertionAttributesUserDetailsService">
	</beans:bean>

	<authentication-manager alias="authenticationManager">
		<authentication-provider ref="casAuthenticationProvider"></authentication-provider>
	</authentication-manager>
	
	<!-- cas 认证失败控制器 -->  
    <beans:bean id="authenticationFailureHandler"  class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">  
        <beans:property name="defaultFailureUrl" value="/login?error=error" />
    </beans:bean>  
    <!-- cas 认证成功控制器 -->  
    <beans:bean id="authenticationSuccessHandler"  class="com.test.AuthenticationSuccessHandler">  
        <beans:property name="defaultTargetUrl" value="/" />
    </beans:bean>
    
    <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
		<beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
        <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
        <beans:property name="filterProcessesUrl" value="/j_spring_cas_security_check" />
	</beans:bean>



具体spring security 与cas 的配置结合,可参考 http://5148737.blog.51cto.com/5138737/1827795