CAS单点登录

CAS单点登录

解决项目部署在多个服务器中时,产生的登录问题 SSO(Single Sign On)

当用户在一个服务器登录后,访问同系统的其他服务器无需再次登录

用户只需要登录一次就可以访问所有相互信任的应用系统。

比如 搜索模块 商品模块 订单模块 部署在不同服务器

用户在搜索服务器登录,那么在访问商品 订单时 也是登录状态

这个问题是传统session无法解决的

cas单点登录基于cookie( 票据 )

单点登录也可以使用session复制+redis解决

1.CAS

从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。

CAS Server 需要独立部署,主要负责对用户的认证工作; CAS服务端就是一个war包

CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。

CAS单点登录

  • 用户通过浏览器访问WEB-A, 此时是未登录状态, WEB-A 重定向到CAS Server进行登录
  • 用户输入账号密码后, CAS Server进行认证, 认证成功后, 将票据(ticket)发送给浏览器
  • 浏览器拿着票据访问WEB-A , WEB-A拿着票据去CAS Server进行验证, 验证成功后, 用户访问WEB-A

访问WEB-B WEB-C时,也是相同处理方式,请求时携带票据到WEB,然后验证,票据存放在cookie中

CAS单点登录

2.修改默认配置

1.修改端口

如果我们不希望用8080端口访问CAS, 可以修改端口 修改tomcat后,在cas的cas.properties 中也要修改

server.name=http://localhost:9100

2.修改https

cas默认是使用https,开发测试时,可以使用http

(1)修改cas的WEB-INF/deployerConfigContext.xml

找到下面的配置
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient"/>
这里需要增加参数p:requireSecure="false",requireSecure属性意思为是否需要安全验证,即HTTPS,false为不采用

(2)修改cas的/WEB-INF/**spring**-configuration/ticketGrantingTicketCookieGenerator.xml

找到下面配置
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
      p:cookieSecure="true"
      p:cookieMaxAge="-1"
      p:cookieName="CASTGC"
      p:cookiePath="/cas" />
参数p:cookieSecure="true",同理为HTTPS验证相关,TRUE为采用HTTPS验证,FALSE为不采用https验证。

参数p:cookieMaxAge="-1",是COOKIE的最大生命周期,-1为无生命周期,即只在当前打开的窗口有效,关闭或重新打开其它窗口,仍会要求验证。可以根据需要修改为大于0的数字,比如3600等,意思是在3600秒内,打开任意窗口,都不需要验证。

我们这里将cookieSecure改为false ,  cookieMaxAge 改为3600

(3)修改cas的WEB-INF/spring-configuration/warnCookieGenerator.xml

找到下面配置
<bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure="true"
p:cookieMaxAge="-1"
p:cookieName="CASPRIVACY"
p:cookiePath="/cas" />
我们这里将cookieSecure改为false ,  cookieMaxAge 改为3600
3.修改数据源

cas默认登录时通过配置文件认证( 配置文件中 默认账号密码为 casuser mellon)

我们修改为通过数据库验证

(1)修改cas服务端中web-inf下deployerConfigContext.xml ,添加如下配置

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
      p:driverClass="com.mysql.jdbc.Driver"  
      p:jdbcUrl="jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=utf8"  
      p:user="root"  
      p:password="123456" /> 
<bean id="passwordEncoder" 
      class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"  
      c:encodingAlgorithm="MD5"  
      p:characterEncoding="UTF-8" />  
<bean id="dbAuthHandler"  
      class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"  
      p:dataSource-ref="dataSource"  
      p:sql="select password from tb_user where username = ?"  
      p:passwordEncoder-ref="passwordEncoder"/>  
然后在配置文件开始部分找到如下配置
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
    <constructor-arg>
        <map>               
            <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
            <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
        </map>
    </constructor-arg>      
    <property name="authenticationPolicy">
        <bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
    </property>
</bean>
其中
 <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
一句是使用固定的用户名和密码,我们在下面可以看到这两个bean ,如果我们使用数据库认证用户名和密码,需要将这句注释掉。

添加下面这一句配置
<entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver"/>

3.cas集成spring security

cas负责认证( 修改cas-servlet.xml 通过数据库认证 )

spring security 负责授权

<!--cas单点登录-->
<dependency>
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-core</artifactId>
    <version>3.3.3</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-cas</artifactId>
</dependency>
<!--  spring security -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>

spring security.xml

  • 匿名可访问的资源
  • 入口点引用 配置过滤器( ref )
  • CAS入口点 配置CAS服务端的URL和回调(本模块)的URL地址
  • 认证管理器 认证提供者 自定义认证类 票据验证器
  • 登出过滤器
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <!-- 匿名访问资源 -->
    <http pattern="/css/**" security="none"></http>
    <http pattern="/js/**" security="none"></http>
    <http pattern="/image/**" security="none"></http>
    <http pattern="/plugins/**" security="none"></http>
    <http pattern="/register.html" security="none"></http>
    <http pattern="/user/add.do" security="none"></http>
    <http pattern="/user/sendCode.do" security="none"></http>
    
    
    <!--   entry-point-ref  入口点引用 -->
    <http use-expressions="false" entry-point-ref="casProcessingFilterEntryPoint">  
        <intercept-url pattern="/**" access="ROLE_USER"/>   
        <csrf disabled="true"/>  
        <!-- custom-filter为过滤器, position 表示将过滤器放在指定的位置上,before表示放在指定位置之前  ,after表示放在指定的位置之后  --> 
        
        <!--自定义过滤器-->
		<!--认证过滤器-->
        <custom-filter ref="casAuthenticationFilter"  position="CAS_FILTER" />      
        <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>  
        <!--登出过滤器-->
        <custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>  
    </http>

    <!-- CAS入口点 开始 -->
    <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">  
        <!-- 单点登录服务器登录URL -->  
        <beans:property name="loginUrl" value="http://localhost:9100/cas/login"/>  
        <beans:property name="serviceProperties" ref="serviceProperties"/>  
    </beans:bean>      
    <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">  
        <!--service 配置自身工程的根地址+/login/cas   -->  
        <beans:property name="service" value="http://localhost:9106/login/cas"/>
    </beans:bean>  
    <!-- CAS入口点 结束 -->

    <!-- 认证过滤器 开始 -->
    <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">  
        <beans:property name="authenticationManager" ref="authenticationManager"/>  
    </beans:bean>  
    <!-- 认证管理器 -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider  ref="casAuthenticationProvider">
        </authentication-provider>
    </authentication-manager>
    <!-- 认证提供者 -->
    <beans:bean id="casAuthenticationProvider"     class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> 
        <!--获取授权信息的-->
        <beans:property name="authenticationUserDetailsService">  
            <beans:bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">  
                <beans:constructor-arg ref="userDetailsService" />  
            </beans:bean>  
        </beans:property>  
        <!--客户端地址的封装-->
        <beans:property name="serviceProperties" ref="serviceProperties"/>  
        <!-- ticketValidator 为票据验证器 -->
        <beans:property name="ticketValidator">  
            <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">  
                <beans:constructor-arg index="0" value="http://localhost:9100/cas"/>  
            </beans:bean>  
        </beans:property>  
        <beans:property name="key" value="an_id_for_this_auth_provider_only"/> 
    </beans:bean>        
    <!-- 自定义认证类 -->
    <beans:bean id="userDetailsService" class="cn.hrh.demo.service.UserDetailServiceImpl"/>  

    <!-- 认证过滤器 结束 -->
    <!-- 单点登出  开始  -->     
    <beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>          
    <beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">  
        <beans:constructor-arg value="http://localhost:9100/cas/logout?service=http://localhost:9103"/>  
        <beans:constructor-arg>  
            <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>  
        </beans:constructor-arg>  
        <beans:property name="filterProcessesUrl" value="/logout/cas"/>  
    </beans:bean>  
    <!-- 单点登出  结束 -->  
</beans:beans>

UserDetailsServiceImpl

/**
 * 认证类
 */
public class UserDetailServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //构建角色集合
        List<GrantedAuthority> authorities=new ArrayList();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new User(username, ""  , authorities);		
    }
}