分布式系统存在诸多子系统,而这些子系统是分别部署在不同的服务器中,那么使用传统方式的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 Server
和 CAS 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);
}
}