一:SSO 基础篇

SSO知识概要 初始篇

前言

受惠于人,授惠于人。

我是在16年4月正式踏进了互联网开发这个行业,作为一个统招大专生和手拿自考本科的实习生来说,在上海入职第一家公司实属幸运! 记得16年的末上海房价疯涨,一天一个价。 金融出现了牛市,闭着眼睛投都能赚钱。表面看起来都是一切美好的开始,18年开始特朗普贸易战,互联网行业开始贩卖焦虑,大数据,区块链,算法,AI,996。互联网科技在这两年的发展速度前所未有。 

快,快点,再快点! 

知识更新换代快!怎么办? 只能每天抽出时间学习。其实我是一个有拖延症的人,属于投入了可以坚持很久一旦松懈就会玩手机到睡觉。 工作还是平常都会在网上找大神的文章,萌生了自己写文章的想法!这个想法从18年开始到现在才开始付出行动,说来惭愧!鉴于网上很多文章将的不是特别详细! 我今天就立个flag。

产出的文章是**通俗易懂**

HTTP 和 Session 、Cookic

HTTP介绍

详细我就不讲了大家可以点进链接查看!我主要讲几点主要的。

  • 基于TCP应用层协议 :是可靠的 3次握手4次挥手

  • 无状态 :最初的时候设计http只是来返回静态页面 所以并不需要维护连接状态

举个例子: 小明 去 商店 买东西

小明 问 服务员 有泡泡糖吗? 服务员 说 有。

小明 问 服务员 多少钱?  服务员懵逼了???

服务员 不记得发生了什么!  

当服务员需要知道每次访问的是谁?上次干了什么?session、cookie这时站了出来!

Session 、Cookie

session/cookic

  • Session:存在服务器端 维护了客户端每次请求的一些信息

  • Cookie:存储在客户端(浏览器)存储量比较小 而且不同的浏览器有不同的限制

    • javax.servlet.http.Cookie

      • name:名称

      • value:值

      • maxAge:单位秒 缓存失效时间 -1000附属直接失效

      • domain:cookic属于域名 (zk.com) 跨域支持

      • path:cookic属于的路径 /就是全路径 默认当前路径(localhost:8080/get_cookie)

      • secure: true只接受 https

      • httpOnly:true cookie只在http中传输

以上 domain maxAge 是后面操作必备的

现在我们在来演示一遍

小明 去 商店 买东西

小明 问 服务员 有泡泡糖吗? 服务员分配一个001编号 并在小本本上记录001的行为然后 服务员 说 有 并且把编号交给小明。

小明 问 服务员 多少钱 并且把编号告诉 服务员! 服务员根据编码知道了小明之前问的泡泡糖 服务员告诉小明泡泡糖价格!

我们把 小明当成浏览器 服务员当前服务器 小本本为Seesion 编号为cookie的信息 我们就不难理解这样设计的初衷了!

Session一致性

首先我们来看一张图

war部署

部署和扩容

左边在我们业务量较小的时候基本的部署模式

右边在业务量增长到单机无法支撑的时候我们就需要扩容(多机部署)

  • 垂直扩容: 升级内存 带宽 主要做硬件升级

  • 水平扩容: 常见的就是增加机器 当一台机器QPS500 两台机器就是1000

  • 增加机器 就会带来数据一致性问题!如数据、缓存、session

互联网开发公司常用的就是水平扩容基本所有服务就是多机部署的!在老东家管易云双十一的时候服务都是20台以上,平常数据迁移也是11台机器部署服务的。老东家大搜车汽车零售软件其实也是双机部署的。但是机器是昂贵的我们还要充分分析系统的瓶颈,提升单机性能,再考虑加机器吧。

多机部署的登入问题

假设现在有个小网站www.ts.com 小明打开电脑打开Chrome浏览器登入

session一致性问题

1.0 小明登入 www.ts.com

1.1 通过负载均衡 路由到0.3机器

1.2 验证密码账号成功 设置isLogin=true

1.3 携带jsession并返回

2.0 再次访问 进入的是0.4这台机器 这个台机器的seesion是没有islogin这个状态的 需要重新登入

要解决seesion一致性问题引出了以下几种方案:

  • 粘性session

  • session复制

  • session统一存储

  • token方式

一:粘性session

何为粘性session? 这个切入点在负载均衡这里。 负载均衡分为两种:

  • 硬件负载均衡: 代表有F5

  • 软件负载均衡:代表有nginx、lvs

    这里只说软件负载,我们正常应用会挂个nginx,通过它我们可以设置转发规则。 客户端IP取hash值%机器数量 = 1 那么下一次请求 IP为112.0.0.1结果还是1, 每次路由都是相同机器。

缺点:如果192.0.0.3192.0.0.4两台机器其中192.0.0.3宕掉,产生单点故障。

二:session复制

session复制

这是Tomcat提供的解决方案,具体是两台tomcat服务器之间进行session数据同步!

缺点: 多台服务器之间数据同步造成性能问题!

三:session统一存储

session统一存储

为了减轻服务器之间同步压力,既然是每台服务器都存储session为什么不统一存储起来,统一的访问呢?

这就是第三种方案session统一存储,存储的介质很多:

  • Mysql

  • Redis

比较多的公司都是通过Redis来实现的,常用的方式我们可以借助spring-session轻松实现统一储存。

spring-boot 工程打个样:官方文档

  1. POM引入坐标:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>


<!--有点坑 需要引入这两包 依赖了内部的api 如下-->
<!--SpringSessionRememberMeServices-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
  1. propertis 文件新增配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#session 存储方式
spring.session.store-type=redis
# 会话超时。如果未指定持续时间后缀,则使用秒。
server.servlet.session.timeout=3600
#sessions flush mode。
spring.session.redis.flush-mode=on-save
#用于存储会话的密钥的命名空间。
spring.session.redis.namespace = spring:session:ts

#Redis服务器主机。
spring.redis.host = 129.28.195.236
#redis服务器的登录密码。
spring.redis.password =
#Redis服务器端口。
spring.redis.port = 6379
  1. 登入web

  2. 我们打开redis客户端可以看到

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> keys *
1) "trojan2"
2) "spring\xc3\xaf\xc2\xbc\xc2\x9asession:sessions:expires:320b113f-820f-470c-8982-5eed9afbdddb"
3) "spring\xc3\xaf\xc2\xbc\xc2\x9asession:sessions:320b113f-820f-470c-8982-5eed9afbdddb"
4) "trojan1"
5) "Back2"
6) "Back3"
7) "Back1"
8) "spring\xc3\xaf\xc2\xbc\xc2\x9asession:expirations:1561205820000"
127.0.0.1:6379>
  1. 看到这里我们已经使用spring-session搭建了一个使用redis统一存储session应用

四:Token方式

我们先考虑个几个问题

  1. 如果浏览器禁用cookie,无法获取jessionid怎么?

  2. 当我们面对app这种平台如何进行登入管理?

  3. 跨域cookie无法传送的怎么办?

衍生了另一种方式 Token 常用方式,它既可以当作请求参数传入也可以放入cookie中!Token就是一串唯一身份标识符。常用的Token一般识用账户唯一信息+加密的key通过加密算法(HSA/MD5)等方式生成一串唯一标识符。

接下来我来介绍一种封装Token框架 JWT(Json Web Token)。

jwt

通过这张官网的图我们可以简单得知 它分为三块:

  • HEADER:头 包含了 加密算法加密类型

  • PAYLOAD:载体 包含了 用户信息(用户id)jwt预设的信息(失效时间)

  • VERIFY SIGNATURE:签名 这个是后台验证的关键

需要的注意 HEADERPAYLOAD 都只是BSAEURL64编码了一下 所以很容易被人破解,请不要存储敏感信息。

不讲虚的我们来点干货,上代码->

  1. 引入Jar包
1
2
3
4
5
6
 <!--jwt 认证-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
  1. 编写工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* jwt TOKEN 生成 解析
*
* @author zhoukun
*/
@Slf4j
public class JwtUtil {

private JwtUtil() {

}

/**
* 加密Key
*/
public static final String SECRET = "c61070db-450b-419e-8ff9-e45fae4f6e3c";
/**
* 发行人
*/
private static String ISSUER = "JackKun";


/**
* 生成Token
*
* @param claims 主体
* @param expireDate 过期时间
* @return
*/
public static String generateToken(Map<String, String> claims, Date expireDate) {
try {
//申明加密算法
Algorithm algorithm = Algorithm.HMAC256(SECRET);

//创建JWT
JWTCreator.Builder builder = JWT.create()
.withIssuer(ISSUER)
.withExpiresAt(expireDate);

//设置主体参数
claims.forEach((k, v) -> {
builder.withClaim(k, v);
});

//返回加密签名 token
return builder.sign(algorithm);
} catch (IllegalArgumentException e) {
log.error("生成Token 非法参数异常! errMsg:{}", e);
throw new RuntimeException(e);

} catch (JWTCreationException e) {
log.error("生成Token 创建JWT异常! errMsg:{}", e);
throw new RuntimeException(e);
}
}

/**
* 验证Token
*
* @return
*/
public static Map<String, String> verifyToken(String token) {
//申明加密算法
Algorithm algorithm = Algorithm.HMAC256(SECRET);

JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
DecodedJWT jwt = verifier.verify(token);

Map<String, String> resultMap = new HashMap<>();
Map<String, Claim> claimMap = jwt.getClaims();
claimMap.forEach((k, v) -> resultMap.put(k, v.asString()));
return resultMap;
}
}

这个工具类稍微比较简陋,朋友可以自行研究研究!具体通过它实现SSO我们先放到后面再讲,这里就不在展开讲了!

引子

上面讲了这么多只是讲了一些基础 后面会讲讲什么是单点登入?切入点?我们怎么来实现?跨域如何解决?