session
会话标识
服务器为一次浏览器访问的所有请求生成一个统一的session,标识这些请求来自同一个用户,存储在内存中
session的ID -> session ID被发送给客户端,并保存在会话级cookie中
session是服务器端生成并维护的,大量的访问生成大量的session对服务器来说是一笔不可忽视的开销
session在以下情况下被删除
- 程序调用HttpSession.invalidate()
- 超时
- 服务端程序终止
cookie
由服务器生成,发送给客户端,在客户的物理硬盘上,以文件的形式存储(持久化cookie),是一种键值对数据
- 好像服务器给每个访问过它的客户脑门上都贴上一张带名字的纸,这张纸就保存在客户脑门上,下次客户来访问,服务器看它脑门上的纸就认识了
会话cookie
存放在客户端浏览器内存中,它的生命周期和浏览器是一致的,当浏览器关闭,会话cookie也就消失了
持久化cookie
存储在客户端硬盘中,持久化的cookie的生命周期是我们在设置cookie时设置的保存时间,session信息时通过session ID获取的,而session ID存储在会话cookie中,当浏览器关闭,则会话cookie消失,所以session ID也就消失了
此时服务器端的session并没有消失,session在以下情况下被删除
- 程序调用HttpSession.invalidate()
- 超时
- 服务端程序终止
session和cookie对比
- session存储在服务器端、cookie存储在客户端,因此session比cookie安全
token
某用户输入了正确的账号密码,成功登录了系统,服务器给他生成一个令牌token,其中包含了用户的user id,之后用户所有的请求中,都要在请求头中携带这个token,服务端按照加密规则进行相应的解密,对token进行验证,可以识别请求来自哪个用户,是不是合法的用户
info
关于Java Web Token详解,放在项目的笔记中
token如果被伪造呢
加盐
对生成的token加上一段只有服务端自己知道的信息,称为盐,添加这段信息的过程称为加盐
- 不建议使用固定字符串作为盐,因为有可能被反推出来,太有规律性了
- 最好为每个用户生成独立的盐,再添加,并且把盐存储起来
加密
将加过盐的token通过摘要加密算法(基于Hash散列加密)进行加密。MD5、SHA256等
基于token的登录验证流程
在没有token的情况下,服务器判断用户是否登录, 完全依赖于sessionId, 一旦其被截获, 黑客就能够模拟出用户的请求。于是我们需要引入token的概念: 用户登录成功后, 服务器不但为其分配了sessionId, 还分配了token, token是维持登录状态的关键秘密数据。在服务器向客户端发送的token数据,也需要加密。于是一次登录的细节再次扩展。
- 客户端向服务器第一次发起登录请求(不传输用户名和密码)。
- 服务器利用RSA算法产生一对公钥和私钥。并保留私钥, 将公钥发送给客户端。
- 客户端收到公钥后, 加密用户密码,向服务器发送用户名和加密后的用户密码; 同时另外产生一对公钥和私钥,自己保留私钥, 向服务器发送公钥; 于是第二次登录请求传输了用户名和加密后的密码以及客户端生成的公钥。
- 服务器利用保留的私钥对密文进行解密,得到真正的密码。 经过判断, 确定用户可以登录后,生成sessionId和token, 同时利用客户端发送的公钥,对token进行加密。最后将sessionId和加密后的token返还给客户端。
- 客户端利用自己生成的私钥对token密文解密, 得到真正的token。
GateWay + JWT + RBAC + Filter
权限模组
实现
UserDetailsService
接口 重写 UserDetails loadUserByUsername(String username) 方法- 在这个方法中,通过
myUserDetailsServiceMapper
根据username
查询角色
和权限
,将角色作为一种特殊的权限添加到权限集合
,前面拼接上ROLE_
即可
- 在这个方法中,通过
自定义
MyRBACService
类,指定一个方法hasPermission
,根据 userDetails 导入权限,权限与 uri 进行正则匹配继承
WebSecurityConfigurerAdapter
,配置类- http.authorizeRequests().anyRequest().access("@rbacService.hasPermission(request,authentication)");
自定义过滤器
MyAuthenticationTokenFilter
,继承OncePerRequestFilter
每个请求只会过滤一次,检查userId
请求头,里面是username
,从网关那来的
GateWay 网关
- 负责
用户名-密码
登录,登录成功username
写入 请求头userId
进行转发- 检查是否访问的是
不需要权限的 uri
- 否则,分发、检查 token,不合法的 token 要求重新登录
- 检查是否访问的是
- 配置转发
继承
AbstractRoutePredicateFactory
,重写- 命名:Xxxx+RoutePredicateFactory
- @Overridepublic Predicate<ServerWebExchange> apply(Config config) {return exchange -> {String requestURI = exchange.getRequest().getURI().getPath();if (config.getFlag().equals("blog")&& (requestURI.startsWith("/blog")// || requestURI.startsWith("/blogContent")// || requestURI.startsWith("/sysorg")// || requestURI.startsWith("/sysrole")// || requestURI.startsWith("/sysmenu")// || requestURI.startsWith("/sysdict")|| requestURI.startsWith("/blogContent"))) {return true; //表示匹配成功}return false; //表示匹配失败};}
application.yaml 进行转发配置
幂等性
幂等性问题解决方案
对业务接口的多次调用产生的效果与业务设计是一致的,不会因为多次重复请求(重复点击)而产生数据偏差
- 查询操作
- 查询一次与查询多次,在数据不变的前提下,查询结果一致,
SELECT
具有天然幂等性
- 删除操作
- 删除也是
天然幂等
,因为删除一次就被删掉了,再删除多次也无所谓,反正都没了- 修改、新增要考虑幂等性问题:
唯一索引
:防止新增脏数据,假设每人只允许买一张显卡,为了防止重复点击买到多个,可以给数据库显卡表
所属用户ID 一列加唯一索引
唯一索引
||唯一组合索引
来防止新增数据存在脏数据Token 机制
:防止页面重复提交traceId
:用 设备号+大致时间+用户标识+操作编号生成,确定唯一的一次操作,记录在 redis 中,重复请求时,发现相同的 traceId,则拒接处理
基于 Token 的防止页面重复提交
业务要求:页面数据只能被点击提交一次
发生原因
:由于重复点击或网络重发等原因,发生数据被重复提交解决方案
:集群采用 token + redis(单线程排队);单 JVM 环境; token + redis 或 token + jvm锁处理流程
:
- 数据提交前向服务申请 token,token 存放到 redis 或 jvm 内存,设置 token 有效时间
- 提交后后台校验 token,同时删除 token,生成新 token 返回
token
的特点:需要删除、一次有效性、可以限流
[注]
: Redis 要用删除操作来判断 token,如果删除成功则代表 token 校验通过
对外提供的 API 如何保证幂等性
- 例:银联付款接口,需要用户携带
source
来源、seq 序列号
source + seq
在数据库做唯一索引,防止多次付款(并发环境下,只能处理一个请求)- 付款前,先执行查询操作
source+seq
,如果不存在才执行,存在说明处理过了,拒绝请求