分布式服务CAS单点登陆详解及Spring-Security整合CAS实现单点登陆

分布式系统存在诸多子系统,而这些子系统是分别部署在不同的服务器中,那么使用传统方式的session是无法解决的,我们需要使用相关的单点登录技术来解决

1. 什么是单点登陆

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统
在这里插入图片描述

2. 什么是CAS

CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目

CAS 具有以下特点:

  • 开源的企业级单点登录解决方案
  • CAS Server 为需要独立部署的 Web 应用
  • CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等

从结构上看,CAS 包含两个部分: CAS ServerCAS Client,CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server,下图是 CAS 最基本的协议过程

在这里插入图片描述

SSO单点登录访问流程主要有以下步骤:

  • 访问服务:SSO客户端发送请求访问应用系统提供的服务资源
  • 定向认证:SSO客户端会重定向用户请求到SSO服务器
  • 用户认证:用户身份认证
  • 发放票据:SSO服务器会产生一个随机的Service Ticket
  • 验证票据:SSO服务器验证票据Service Ticket的合法性,验证通过后,允许客户端访问服务
  • 传输用户信息:SSO服务器验证票据通过后,传输用户认证结果信息给客户端

3. CAS单点登陆Demo-第一个系统

  • 首先Maven项目 cas_demo1客户端系统

  • 在cas_demo1 pom文件中添加依赖,并将此项目打包方式改成war包

<packaging>war</packaging>

    <dependencies>
        <!-- cas -->
        <dependency>
            <groupId>org.jasig.cas.client</groupId>
            <artifactId>cas-client-core</artifactId>
            <version>3.3.3</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>9001</port>
                    <path>/</path>
                </configuration>

            </plugin>
        </plugins>
    </build>
  • 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"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 -->
    <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>
    <!-- 该过滤器负责用户的认证工作,必须启用它 -->
    <filter>
        <filter-name>CASFilter</filter-name>       <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <!-- 这里填写你自己的cas服务器登录地址-->
            <param-value>http://192.168.25.120:9000/cas/login</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <!--这里的server是服务端的IP+端口号 这里是9001 -->
            <param-value>http://localhost:9001</param-value>
        </init-param>
        <!-- 首先访问本地服务器地址,如果没有登录那么跳转到上面的cas登陆服务实现登陆-->
    </filter>
    <filter-mapping>
        <filter-name>CASFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
    <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>http://192.168.25.120:9000/cas</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://localhost:9001</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>
</web-app>
  • 创建index.jsp写代码
<%--
  Created by IntelliJ IDEA.
  User: jinsa
  Date: 2020/2/7
  Time: 13:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>欢迎使用单点登陆CAS--ONE</title>
</head>
<body>
<h1>欢迎 :  <%=request.getRemoteUser()%> 访问单点登陆系统--ONE ONE</h1>
</body>
</html>


4. CAS单点登陆Demo-第二个系统

  • 创建 cas_demo2系统
  • 只需要把坐标依赖复制过来,改一下本地启动端口
  • 复制webapp文件夹 ,把web.xml配置文件的启动端口改掉即可
  • jsp文件加以区分即可
<%--
  Created by IntelliJ IDEA.
  User: jinsa
  Date: 2020/2/7
  Time: 13:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>欢迎使用单点登陆CAS--TWO</title>
</head>
<body>
<h1>欢迎 :  <%=request.getRemoteUser()%> 访问单点登陆系统--TWO TWO</h1>
</body>
</html>


5. 测试是否成功

  • 把两个服务都启动起来,访问本地系统: http://localhost:9001
  • 由于没有登录,所以跳转到CAS登录系统,这里输入用户名密码后访问到demo1系统

在这里插入图片描述

  • 接下来在访问第二个系统 9002, 此时应该是不需要登录的

在这里插入图片描述

  • 访问流程图

在这里插入图片描述

6. 单点退出

  • 只需要访问 服务器地址+cas启动端口/cas/logout 即可退出
  • http://192.168.25.120:9000/cas/logout

在这里插入图片描述

7. 配置单点退出后跳转到其他页面

  • 修改cas/WEB-INF系统的配置文件cas-servlet.xml

在这里插入图片描述

  • 修改后测试代码编写,jsp就行
<a href="http://192.168.25.120:9000/cas/logout?service=http://www.baidu.com">单点退出</a>

在这里插入图片描述

  • 点击后成功跳转到百度 ,说明配置成功

在这里插入图片描述

8. 配置数据源-登录时查询数据库用户信息

  • 修改cas服务端中web-inf下deployerConfigContext.xml,添加如下配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
			  p:driverClass="com.mysql.jdbc.Driver"  
			  p:jdbcUrl="jdbc:mysql://localhost:3306/你的数据库名称?characterEncoding=utf8"  
			  p:user="root"  
			  p:password="root" /> 
	<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"/>  
  • 将配置文件中 64 行 key-ref修改成自己的数据源,这里是 dbAuthHandler

在这里插入图片描述

  • 在 -cas/webapps/cas/WEB-INF/lib/下添加三个jar包,重启即可

在这里插入图片描述

9. 更换CAS默认的登录页面

  • 把你的html页面添加到
  • /home/pyg/passport-cas/tomcat-cas/webapps/cas/WEB-INF/view/jsp/default/ui/目录下
  • 并把casLoginView.jsp 更换成你自己的html页面,注意名字要一样,可以把之前的备份
  • 把你自己页面需要的css js image拷贝到 cas根目录下的目录中

9.1 更改页面内容

  • 我这里把html页面更改为CAS登录页面了,以html为例在你的页面中添加以下内容
<%@ page pageEncoding="UTF-8" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
  • 修改你html页面中的form标签 改成之前默认页面的标签 样式用你自己的
# 默认标签 添加你自己的class就行
<form:form method="post" id="fm1" commandName="${commandName}" htmlEscape="true">

<form:errors path="*" id="msg" cssClass="errors" element="div" htmlEscape="false" />

# 结束标签  </form:form>
  • 修改用户名输入框
# 这是CAS默认的 添加你自己的class
<form:input cssClass="required" cssErrorClass="error" id="username" size="25" tabindex="1" accesskey="${userNameAccessKey}" path="username" autocomplete="off" htmlEscape="true" />
  • 修改密码框
# 同上
<form:password cssClass="required" cssErrorClass="error" id="password" size="25" tabindex="2" path="password"  accesskey="${passwordAccessKey}" htmlEscape="true" autocomplete="off" />
  • 修改登录按钮
# 这里的变量是默认的 更改你自己的内容 class
<input type="hidden" name="lt" value="${loginTicket}" />
<input type="hidden" name="execution" value="${flowExecutionKey}" />
<input type="hidden" name="_eventId" value="submit" />
<input class="sui-btn btn-block btn-xlarge btn-danger" accesskey="l" value="登陆" type="submit" />
  • 重启tomcat测试,我这边显示正常,证明修改成功

10. CAS与SpringSecurity集成

  • pom文件添加依赖
<dependency>  
	   <groupId>org.springframework.security</groupId>  
	   <artifactId>spring-security-cas</artifactId>  
	   <version>4.1.0.RELEASE</version>  
</dependency>     
<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> 
  • 创建spring-security.xml配置文件
<?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="/register.html" security="none"></http>
	<http pattern="/css/**" security="none"></http>
	<http pattern="/img/**" security="none"></http>
	<http pattern="/js/**" security="none"></http>
	<http pattern="/plugins/**" security="none"></http>
	<http pattern="/data/**" security="none"></http>
	<http pattern="/user/register/**" security="none"></http>
	<http pattern="/user/sendSms/**" security="none"></http>

	<!-- 整合CAS -->
	<!--   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   这里的地址改成CAS的服务地址-->
		<beans:property name="loginUrl" value="http://192.168.25.120:9000/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:8086/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 为票据验证器  这里的地址改成CAS的服务地址-->
		<beans:property name="ticketValidator">
			<beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
				<beans:constructor-arg index="0" value="http://192.168.25.120:9000/cas"/>
			</beans:bean>
		</beans:property>
		<beans:property name="key" value="an_id_for_this_auth_provider_only"/>
	</beans:bean>

	<!-- 认证类 自己定义-->
	<beans:bean id="userDetailsService" class="自己定义"/>

	<!-- 认证过滤器 结束 -->
	<!-- 单点登出  开始   这里的地址改成CAS的服务地址 -->
	<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://192.168.25.120:9000/cas/logout?service=http://192.168.25.120:9000/cas/login"/>
		<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>
  • 创建认证类
package com.pyg.user.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.List;

/**
 * @ Description
 * @ auther          宁宁小可爱
 * @ create          2020-02-07 16:20
 */
public class UserDetailServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 创建集合 封装角色权限 查询数据库工作已经交给CAS完成了
        List<GrantedAuthority> list = new ArrayList<>();
        list.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new User(username,"",list);
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我三胖哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值