1.1 会话的概念
1.1.1 基础定义
- 一旦用户和服务器交互,服务器(tomcat)就会为用户创建一个session,同时前端会创建一个jessionid,每次交互都会携带
- 如此一来服务器只要接收到用户请求的时候,就可以拿到jessionid,并根据id在内存中找到对应的会话session,当拿到session会话后,那么我们就可以操作会话了
- 会话存活期间,我们就能认为用户正一直使用着网站状态,一旦session超时过期,就可以判断用户已经离开网站
- 相互之间的判断就是通过jessionid判断的,session中可以保存不同用户的信息
1.1.3 无状态会话
1.1.4 有状态会话
1.1.5 Tomcat会话
2. 分布式会话
2.1 流程分析
2.2 实现
- 前后逻辑
- 前端: 使用cookie记录token信息(小程序和app使用本地缓存)
- 后端: Redis按照uid - token类似的逻辑存储
- 令牌(token)思考
- token可以让用户在一段时间内是免登录的
- 前端是放在cookie中,随着http请求发送到后端进行验证
-
3. 单点登录(SSO)
https://blog.csdn.net/xiaoguan_liu/article/details/91492110
3.1 同级顶级域名
3.2 不同顶级域名
CAS:Central Authentication Service 单点登录/中央认证服务
4. 鉴权
4.1 权限的概念
4.1.1 什么是权限
(1) 权限的定义
(2) 静态权限
静态权限:例如 普通用户、vip用户、特级用户,如果我们想增加一个用户必须要修改代码,也就是说分组是在项目设计之初就定制好了
(3) 动态权限
-
(4) 超权概念
- 横向越权: 横向越权指的是攻击者尝试访问与他拥有相同权限的用户的资源,比如: 通过知道别的用户的id,修改别的用户的个人信息
- 纵向越权: 纵向越权指的是一个低级别攻击者尝试访问高级别用户的资源
用户的id不能在前台显示的传递: 不能在header、path路径、查询参数中传递,可以使用Threadlocal / redis 存储
4.2 权限的设计
三个方面:用户、分组(角色)、权限
-
4.3 登录鉴权
4.3.1 单令牌机制
单令牌机制已经满足web程序的登录和权限交互,但是双令牌的体验更好
- 小程序只需要单令牌即可,因为小程序的token一旦过期,小程序会再次尝试登录,刷新token,而且这个流程是静默的,且用户无感知
单令牌的缺陷:当token的时间过了,我们在发起请求,会直接跳转到登录的界面
4.3.2 双令牌机制
两个令牌的标志:
access_token
、refresh_token
- 双令牌帮助无感知登录的流程
- 在token快过之后,访问被拒接
- 检测是否存在refresh_token,存在则判断refresh_token是否过期
- refresh_token没问题,则调用刷新方法,重新获取一个access_token
- 系统重发被决绝的请求,并且更新refresh_token的有效期
- 一般情况下refresh_token时间远大于access_token的时间
例如:access_token 2小时 refresh_token 7天
5. 鉴权实战
5.1 鉴权的实战理解
登录是最简单的鉴权,例如系统内的所有接口,只有登录才能访问,后续访问也不分什么角色,这种比较简单,适合自己写着玩,所以这里也做记录
- 真正的权限管理,登录只是第一步,包含整体的权限划分,异常权限处理…..
- 后面的实战如果是鉴权,则表明有登录也有权限划分,如果是登录,就是简单的登录访问接口
5.2 小程序的鉴权
- 登录验证
- 自己的小程序像微信获取到一个code码
- 带着code访问自己项目的api
- 调用微信的服务器验证
- 生成token,存储在小程序的本地缓存中
- 权限验证: 实现方式还是单令牌实现
- token随着http请求发送到后端进行验证
- 判断token是否正确(是否过期、是否正确….)
-
5.3 web登录
5.4 web鉴权
6. 令牌的常见实现
-
6.1 UUID
Jdk自带的方法::
UUID.randomUUID().toString();
6.2 雪花算法
6.2.1 基本原理
一个
64bit
的long
类型的数字- 引入了时间戳,保持自增
雪花算法位数解析
雪花算法得到的数字较长,传到前端后,精度丢失
- 库中存的值:
23754851322302474
- 后端取的值:
23754851322302474
- 前端得到值:
23754851322302470
,数据被四舍五入了6.2.2.2 解决方式
```java 单个字段 @JsonSerialize(using= ToStringSerializer.class) private Long id;
统一配置类 package com.jiawa.wiki.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration public class JacksonConfig { @Bean public ObjectMapper jacksonObjectMapper (Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); return objectMapper; } }
<a name="tJcBI"></a>
## 6.3 Jwt
1. 全称为`json web token`,说白了是什么呢? 就仅仅只是一个字符串而已,例如:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.OLvs36KmqB9cmsUrMpUutfhV52_iSz4bQMYJjkI_TLQ`
1. jwt 包含了三个主要部分:` Header.Payload.Signature`,以" . "来进行分
1. `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`
1. `eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0`
1. `OLvs36KmqB9cmsUrMpUutfhV52_iSz4bQMYJjkI_TLQ`
3. **Header:头部声明token的类型,这部分主要存储关于签名算法的信息,通常包含两个部分:token类型和采用的加密算法,如下 👇 然后使用Base64Url编码组成了Header部分**
```json
{
"alg": "HS256",
"typ": "JWT"
}
Payload: 是存储一些数据的地方
- 它其实就是一个数据实体,俗称Claim,jwt并不强制使用,它默认这一部分数据为业务数据,是系统业务需要的数据,可有可无,可多可少,然后使用Base64Url编码组成了Payload部分
- 你可以定义token的签发者、签发过期时间、生效时间等一系列属性,以及auth0包对应的方法名称主要包含如下👇
- 还可以添加自定义的属性,服务端收到Token的时候也可以对Payload中的信息验证,例如: 某个token的签发者是”feign-api”,加入某个接口只能允许”gateway-api”签发的token,那么在做权限验证的时候就可以加入lssuer的判断逻辑
iss(签发者、发行方):issued
exp(过期时间戳)
sub(面向的用户)
aud(接收方)
iat(签发时间)
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
- 引入一个数据安全机制,减少敏感数据的暴露程度,因此需要对三个环节做严格控制
- 数据端加密:就像颁发CA证书一样,必须由一个中心机构用密钥对明文进行加密。这里也一样,需要在生产环境中创建一个特定的服务,专门做加密操作。最重要的是,加密所用的密钥只能保存在那台机器里,不能对外暴漏。
- 数据存放端:数据存放(如GitHub、db)只能保存在加密过后的数据
数据解密端:由于密钥都保存在数据加密端,因此数据解密和数据加密必须放在同一个地方进行,加密数据在传给客户端之前要经过解密,不能将密钥下方到客户端机器上进行解密
7.2 对称加密(对称密钥)
对称加密算法要求发送者和接收者在进行信息交换之前,商定一个密钥,即加密和解密都使用同一个密钥来进行
- 如果大家上学的时候选修过密码学就会知道,对称加密算法运算速度快,比较高效
潜在风险:密钥只能分发给绝对信任的 client(授信服务),否则任何获得密钥的人都可以使用该密钥进行通信
7.3 非对称加密(非对称密钥)
7.3.1 基本原理
非对称加密算法需要生成一个密钥对,包括(公钥 & 私钥)
- 它是成对出现的,就像一把锁和一把钥匙,只有特定的钥匙才能开启对应的锁
- 使用: 公钥用来对特定的信息进行加密,使用对应的私钥进行解密
非对称算法复杂度强,加解密速度没有对称快,但是解决了密钥分发安全性问题
7.3.2 使用技巧
流程: 服务A ——> 数据发送 ——> 服务B ——> 数据返回 ——> 服务A
- 谁接受服务,谁出一对密钥(公钥 & 私钥)
- 首先协商出两队公钥和私钥
A向B发送数据时候,A采用B提供的公钥进行加密,B采用自己保留的私钥进行解密
7.3.3 非对称的公钥是公开的吗?
首先公钥也是密钥的一部分,它所谓的”公开”是针对授信访问者来说的,在这个圈子里面是公开的
假如你的服务可以被任何人调用,那么这个公钥则是可以被任何人知道的
7.3.4 频繁调用鉴权服务的令牌校验接口产生的问题?
一般的鉴权服务不是集成在网关层的,那么每一次的令牌的校验都需要发送一个http请求到鉴权服务。那么每一次方法的调用都会触发请求,并发 + RT(响应时间)都会增加
- 解决方式:令牌的颁发采用非对称加密,然后令牌的校验下放到网关层,网关层拿着私钥进行解密,然后直接在网关层校验令牌