SSO单点登录技术简介

快速上手与原理探究

概述

什么是SSO?

单点登录(Single Sign On)通过SSO整合多个应用,可以避免进入多个应用重复登录、此外也可以避免系统安全问题。

如淘宝和天猫,如果登录一个,再登录天猫,天猫是不需要登陆的

什么是CAS

CAS(Central Authentication Service,统一身份认证服务)是一种实现SSO的一种成熟实现方案,一般由服务端与客户端组成。

原来是Yale大学2004年发起的开发项目。

通过引入CAS 系统架构就演变成这样了

image-20230714104844399

这样单个系统一旦需要认证服务,都会前往CAS server 去认证

所以层架构上看,一定要有一个 CAS server(一般需要单独部署),而原来需要认证的单个服务就是 CAS client 负责处理对客户端受保护资源的访问请求,需要进行认证时就会重定向到 CAS Server

CAS原理 (协议流程)

CAS基本的协议过程

CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。

image-20230717095550279

CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。

在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。

另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。

流程图形式(来自B站UP@自由的加百列

首次登录

这里的认证中心即CAS server, 而A系统与B系统是指被 认证中心管控下的 CAS client

image-20230717143008517

当用浏览器第一次访问A系统,发现妹有token 或者 token失效 (token验证通过但是发现有效期过了)

A系统的CAS client 会将通知浏览器重定向 认证中心

认证中心发现 妹有cookie 或者cookie失效重定向到统一登录页

我们输入用户名密码后提交

认证中心完成认证后,并通知浏览器重定向到 A系统并在url中携带ticket

A系统获取ticket , 并与认证中心交互完成认证,认证中心向A系统颁发令牌(token)(浏览器不可见)

A系统通知浏览器认证通过并给浏览器颁发token

这样,浏览器就能凭借token与A系统正常交互了。

PS:在浏览器端你能看到网址的变化是

1、输入www.a.com (尝试获取A系统资源,但发现妹有令牌)

2、自动跳转到认证中心 www.cas.com/ (发现妹有cookie)

3、自动跳转到登录页 www.cas.com/login(输入用户名密码)

4、点击登录跳转 www.a.com?ticket=123abc (a系统拿ticket取认证中心认证)

5、认证通过后给我们颁发令牌,我们拿令牌获取资源 www.a.com/index

再次登录

image-20230717142908405

当我们用浏览器第一次访问B系统,B系统发现妹有token 或者token失效

B系统通知浏览器,让浏览器重定向 认证中心

认证中心发现浏览器已经有了cookie ,并且cookie有效,直接跳过登录完成认证,会通知浏览器重定向B系统并在url中携带ticket

B系统获取了ticket在后台与认证中心交互获取令牌完成认证,B系统将获取的令牌(token)再颁发给浏览器

这样浏览器就能正常访问B系统资源了

PS:在浏览器端你能看到网址的变化是

1、输入 www.b.com (尝试获取b系统资源,但发现妹有令牌)

2、跳转认证中心 www.cas.com 直接完成认证

3、跳转b系统并携带 ticket www.b.com?ticket=123abc (b系统拿ticket取认证中心认证)

4、认证通过后给我们颁发令牌,我们拿令牌获取资源 www.b.com/index

CAS Server 服务端

CAS 服务端软件包下载

前往 服务端的 overlay包

https://github.com/apereo/cas-overlay-template

下载仓库的压缩包

服务端项目构建:

再命令行状态下,再文件夹 cas-overlay-template-master种执行命令:

build.cmd.package 
# build.cmd 是包中的一个软件

注意:

1、jdk 大于等于1.8

2、maven 大于等于 3.5

3、要联网还要下载依赖

最后会生成一个war包供使用

搭建tomcat HTTPS支持

CAS采用HTTPS协议处理用户请求,所以我们需要配置Tomcat支持HTTPS协议

1、生成密钥库

我们采用JDK自带的keytool工具生成密钥库

别名java1234 存储路径 如 D:\cas\keystore

keytool -genkey -v -alias java1234 -keyalg RSA -keystore E:\other\CAS\keystore\java1234.keystore
输入密钥库口令:666666
您的名字于姓氏:java1234.com
您的组织单位名称:java1234.com
您的组织名称是什么:java1234.com
您所在的城市或区域名称是什么:beijing
您所在的省市自治区名称是什么:beijing
该单位的双字母国家代码是什么:cn
CN=java1234.com,OU=java1234.com *****
是否正确?y
输入java1234的密钥口令《如果于密钥库口令相同按回车》按回车:回车

image-20230715163538562

2、从密钥库里导出证书

keytool -export -trustcacerts -alias java1234 -file E:\other\CAS\keystore\java1234.cer -keystore E:\other\CAS\keystore\java1234.keystore

输入密钥库口令:666666

这样就再目录里面生成了证书文件(cer文件)

image-20230715163825125

3、将证书导入到JDK证书库

keytool -import -trustcacerts -alias java1234 -file E:\other\CAS\keystore\java1234.cer -keystore "C:\Program Files\Java\jdk1.8.0_271\jre\lib\security\cacerts"

密码:changeit

4、tomcat 配置http支持

image-20230715165651842

启动tomcat

就可以通过https来访问tomcat了!

CAS server 配置

CAS server war下载

CAS的github 网址:https://github.com/apereo/cas

CAS 服务端:

https://github.com/apereo/cas-overlay-template

可以在这里将所需要的 CAS 服务端代码clone下来打包发布

https://repo1.maven.org/maven2/cas/

CAS的maven仓库 亦可以用下面的war包

https://repo1.maven.org/maven2/org/apereo/cas/cas-server-webapp-tomcat/5.3.14/

下载:cas-server-webapp-tomcat-5.3.14

把下载的war包放在 tomacat 安装目录 /webapps下

把war包放tomcat下,启动tomcat会自动解压,我们把名称改成cas,方便访问;

访问:https://localhost:8443/cas

cas 8443端口

CAS server 配置

配置hosts: C:\Windows\System32\drivers\etc

127.0.0.1 java1234.com

可以通过域名访问:

https://java1234.com:8443/cas

image-20230715175129472

用户名再配置文件中

/webapps/cas/WEB-INF/classes/ 中的application.properties 中获取

最后一行由默认接收用户

cas.authn.accept.users=casuser::Mellon

即用户名 casuser 密码 Mellon

登录

配置日志

tomcat/webapps/cas/WEB-INF/classes/ 中的log4j2.xml文件中

可以修改日志存放的路径

image-20230715175732733

配置数据源 数据库用户认证

定义数据库

定义一个用来用户认证的数据库,数据表中包含用户名和密码选项

CREATE DATABASE /*!32312 IF NOT EXISTS*/`db_sso` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `db_sso`;
/*Table structure for table `t_cas` */
DROP TABLE IF EXISTS `t_cas`;
CREATE TABLE `t_cas` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
/*Data for the table `t_cas` */
insert  into `t_cas`(`id`,`username`,`password`) values (1,'java1234','123456');

修改application.properties配置文件

注释掉写死的认证用户

加上jdbc数据源配置

# cas.authn.accept.users=casuser::Mellon
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQL5Dialect
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/db_sso?serverTimezone=GMT
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=123456
cas.authn.jdbc.query[0].sql=select * from t_cas where username=?
cas.authn.jdbc.query[0].fieldPassword=password
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver

加上jdbc驱动包以及支持jar

把课程资料里面向相关依赖包放进 tomcat/webapps/cas/WEB-INF/lib目录中

image-20230717083357104

点击tomcat 中 bin 目录中的staerup.bash 启动tomcat

https://localhost:8443/cas

发现原来配置文件中的已经无法登录了,用数据库中的便可以登录了。

CAS加密校验

修改配置文件

增加

cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
#MD5加密策略
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5

默认密码加密方式md5

数据库生成下md5密码

将密码都进行md5加密后存进数据库

SELECT MD5('123456');

将这段结果把原来的密码替换

重新启动

用户名密码不变可以继续登录(默认帮我们进行md5加密然后进行匹配)

CAS Client 客户端

CAS Client+SpringBoot客户端整合搭建

假设采用父子模块的方式搭建

新建父项目sso-sys

新建一个maven项目

image-20230717085018282

image-20230717085105839

然后在这个父项目中加一些依赖管理和规范

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.java1234</groupId>
    <artifactId>sso-sys</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <cas.version>2.3.0-GA</cas.version>
        <spring-boot.version>2.3.4.RELEASE</spring-boot.version>
    </properties>

    <modules>
        <module>common-sys</module>
        <module>hr-sys</module>
        <module>crm-sys</module>
        <module>fd-sys</module>
        <module>oa-sys</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>net.unicon.cas</groupId>
                <artifactId>cas-client-autoconfig-support</artifactId>
                <version>${cas.version}</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

新建子项目crm-sys

右键父项目 新建模块(子项目)

image-20230717085700833

pom文件中加入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sso-sys</artifactId>
        <groupId>com.java1234</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>crm-sys</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
<!-- 客户端依赖是必须的-->
        <dependency>
            <groupId>net.unicon.cas</groupId>
            <artifactId>cas-client-autoconfig-support</artifactId>
        </dependency>
<!-- cas支持-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-cas</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
        </dependency>

    </dependencies>

</project>

application.yml

server:
  port: 7777

cas:
  server-url-prefix: https://java1234.com:8443/cas
  server-login-url: https://java1234.com:8443/cas/login
  client-host-url: http://java1234.com:7777
  validation-type: cas3

指定端口7777

resources 下面新建templates

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>crm客户关系</title>
</head>
<body>
    欢迎:&nbsp;&nbsp;进入crm客户关系;

</body>
</html>

controller

package com.java1234.controller;

import org.jasig.cas.client.validation.Assertion;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;

/**
 * @author java1234_小锋
 * @site www.java1234.com
 * @company Java知识分享网
 * @create 2020-10-08 19:44
 */
@Controller
public class IndexController {

    /**
     * 网站根目录请求
     * @return
     */
    @RequestMapping("/crm")
    public ModelAndView root(HttpSession session){
        ModelAndView mav=new ModelAndView();
        mav.setViewName("index");
        return mav;
    }


}

启动类

package com.java1234;
import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

/**
}

 * 开启CAS @EnableCasClient
 */

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableCasClient
public class CrmSysApplication {

    public static void main(String[] args) {
        SpringApplication.run(CrmSysApplication.class, args);
    }
}

启动项目(7777)端口

http://localhost:7777/crm 访问首页

解释一下配置

cas:
  server-url-prefix: https://java1234.com:8443/cas  #CAS 服务端地址
  server-login-url: https://java1234.com:8443/cas/login # CAS 登陆地址
  client-host-url: http://java1234.com:7777 # 登陆成功后回调的地址,该服务首页地址
  validation-type: cas3 # cas类型 2已经淘汰

在SpringBoot启动类上加@EnableCasClient 后便可以登陆了

PS:如果不加@EnableCasClient 那么登录的就是普通Springboot项目,直接进入该系统

现在加了@EnableCasClient 再次输入 localhost:7777/crm 不会直接访问index.html 而是会转到 CAS 服务端

image-20230717092441795

这里报了错是因为?

  • CAS只支持默认只支持https请求不支持 http请求。而我们CAS登录请求的地址这里是(看上面的url)是 http://java1234.com:8443

  • 可以修改配置来进行修改

  • 1、在tomcat/webapps/cas/WEB-INF/classes/service 下面进行配置 修改里面的HTTPSandIMAPS-10000001.json

    • 更改里面的serviceId 加一个http即可
  • 2、在tomcat/webapps/cas/WEB-INF/classes/application-properties (就是刚开始配置数据源的那个地方)在最后加一个

    • cas.tgc.secure=false

      cas.serviceRegistry.initFromJson=true

再次启动tomcat

发现正常跳转到登录界面,输入正确的用户名密码后就可以访问index.html了

简单回顾一下CAS基本的协议过程

CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。

image-20230717095550279

CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。

在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。

另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。

CAS 客户端获取 用户名

有时客户端服务登陆后需要回显用户名,这个用户名怎么获取呢?

我们在上面的 CAS协议执行流程可以看到在认证通过后,CAS client 可以从 CAS server去获取登录认证的信息比如 用户名

thymeleaf(java的模板引擎)页面可以通过 ${session.const_cas_assertion_.principal.name} 取值

具体可以看看源码怎么找到的

可以在client项目依赖包(cas-client-core-3.5.1.jar)中Servlet3AuthenticationFilter中看看

image-20230717103328640

image-20230717103853535

可见他将 principal.getAssertion() 的结果封装到了 session里面,看看 principal.getAssertion() 的返回值是什么

image-20230717104014787

它实现了 SimplePrincipal (里面封装了name属性)所以本质上就是对name等基本属性的封装

那么在 全段就可以用${session._const_cas_assertion_.principal.name}来获取用户名了

<font th:text="${session._const_cas_assertion_.principal.name}"></font>

登陆成功后显示

image-20230717104322901

CAS 客户端统一注销功能

cas注销方式

我们通过重定向到cas服务的logout接口,来实现统一注销

增加 controller代码

/**
 * 注销
 * @return
 */
@RequestMapping("/logout")
public String logout(){
    return "redirect:https://java1234.com:8443/cas/logout";
}

在index.html 中加入按钮

<a href="/logout">安全退出</a>

CAS 单点登录实现测试

在CAS父项目中添加三个系统 crm、fd、hr

实现下面的功能

image-20230717105042530

image-20230717105230451

我们点击任意一个系统、进入系统如果发现是没有登陆的就会跳转到 servcer 的登录界面,我们输入永明密码后再跳转回来原服务

就不写了

CAS server 登录/登出界面修改

CAS server 用springMVC + webflow 实现的

Spring Web Flow 构建与SpringMVC 之上允许实现Web 应用程序的流程,流程封装了一系列的步骤,指导用户执行某些业务任务,它跨越多个HTTP请求,具有状态处理事务数据,可重用,并且可能是动态的,并且本质上是长期运行的。

登录和等处的界面修改步骤

webflow

再 tomcat/webapps/cas/WEB-INF/classes/webflow/

image-20230717110015108

里面一个login 一个logout,这里是业务逻辑,暂时不改

static

static下主要是一些静态文件包括css样式js脚本、images图片等

浏览器的icon(标签页做左边的图标 修改 favicon即可)

templates(修改)

去掉head foot

在classes/templates下

我们用 idea open打开 cas目录

打开 templates下面的layout.html

尝试简单修改一下布局(把 head和foot 注释掉)

image-20230717111348914

重启

image-20230717111727565

head foot 已经去掉了,现在再尝试去掉右边的,怎么去掉呢?

右边可以看到 Links to CAS Resources 我们复制一下,进入全局搜索

edit->Find->Find in path 粘贴即可找到

将notices 部分注释掉

image-20230717131219376

然后在注释中的dev部分添加自己的图片,然后添加自己的图片样式

image-20230717131342045

再用户名密码下面有一段提示信息,这里在哪里修改呢?

  • 在loginform.html里面
  • loginsiderbar.html 里面

image-20230717113138190

它默认读取的优先级应该是中文配置所以最终读取的是 classes目录下面的message_zh_CN.properties 找到里面的screen.welcome.security 选项,修改为自己的中文对应的语句(注意,这里应该改为 \unicode 编码形式,前往 中文转 unicode 编码网站自行替换)若把连接取消则取的是 <p>标签之间的文本内容

最终实现的界面如下

image-20230717131457778