Spring Cloud Gateway作为网关在请求得到路由前进行过滤并分配请求到他指定的路由,Spring Cloud Alibana Nacos作为服务注册使得网关能够服务发现,分配路由,jwt作为token令牌解决传统的单点登录设置cookie和session登录方式,也解决了分布式情况下登录问题
1. 整体实现
虽说token无状态,减少交互数据库,但token无法主动失效,用户注销情况下,仍可以拿着token请求服务器直接登录,故集合redis,加入一个黑名单,当用户注销时,token就加入黑名单,使用户无法直接操作,只能发送重新登录请求
1.1. 响应设计
token | 响应状态 |
---|---|
无token | 50000 - 无访问权限 |
有token,token格式不正确 | 50008 - 非法token |
有token,用户持有的token已被加入到黑名单,即用户被注销 | 50010 - 用户已登出 |
有token,用户正在使用的token和服务端保存的用户正在使用的不一致 | 50010 - 用户已登出 |
有token,用户在线时间超时 | 50014 - token失效 |
1.2. redis设计
JWT_USERNAME::id
字符串类型 存入 用户名
用于保证单端登录
有效时间为用户免登录时间
JWT_TOKEN
Hash类型 存入 id-token
用于记录当前用户【正在使用】的token
JWT_BLACKLIST::group
字符串类型 存入 token
group来自token的载荷中UUID生成的group,用于唯一的对应该token
用于注销,重新登录,刷新操作导致用户被注销,token未失效时,使token自动失效
判断token时,当黑名单中拥有该token,则返回用户被注销
1.3. 登录操作中redis操作
1.3.1. 登录-login
验证账号密码后,验证 【get JWT_USERNAME::id】是否有值,有值则不能登录
无值,用户名存入【set JWT_USERNAME::id username EX time】,防止其他端登录
生成token,存入【hset JWT_TOKEN id token】,保存正在使用的token
返回token给客户端
1.3.2. 登出-logout
删除用户名【del JWT_USERNAME::id】
删除用户当前使用的token 【hdel JWT_TOKEN id】
将token加入黑名单【set JWT_BLACKLIST::group token EX time】
1.3.3. 刷新-refresh
当用户注销,token过期情况下,token可以进行刷新,重新获得免登录权限
将token加入黑名单 【set JWT_BLACKLIST::group token EX time】
重新生成token
更新用户当前使用的token 【hset JWT_TOKEN id token】
更新用户名的有效时间【expire JWT_USERNAME::id time】
返回token
1.3.4. 重新登录-relogin
当用户篡改token,导致token和redis中用户使用的token不一致,但用户名依旧有效情况下,用户无法登录,无法注销,进行重新登录
验证账号密码
删除用户名【del JWT_USERNAME::id】
删除用户当前使用的token 【hdel JWT_TOKEN id】
将token加入黑名单【set JWT_BLACKLIST::group token EX time】
用户名存入【set JWT_USERNAME::id username EX time】,防止其他端登录
生成token,存入【hset JWT_TOKEN id token】,保存正在使用的token
1.3.5. 获取用户信息-getInfo
获取token中的用户信息,不交互redis
1.4. 网关过滤登录路由
网关过滤 | 路由 |
---|---|
直接放行 | /jwt-client/login |
无token | |
有token,token格式不正确 | |
else | /jwt-client/relogin,/jwt-client/refresh |
有token,用户持有的token已被加入到黑名单,即用户被注销 | |
有token,用户正在使用的token和服务端保存的用户正在使用的不一致 | |
有token,用户在线时间超时 | |
else | /jwt-client/logout |
else | /jwt-client/getInfo |
2. 项目
2.1. jwt-api 公共接口类
2.1.1. Maven
1 | <!-- 简化实体类 get/set方法--> |
2.1.2. 定义实体类,统一返回类型
2.1.2.1. Admin
1 | import lombok.Data; |
2.1.2.2. R
1 | import io.swagger.annotations.ApiModel; |
2.1.2.3. JwtClient
1 | public class JwtConstant { |
2.2. jwt-Gateway web项目
2.2.1. Maven依赖
1 | <dependency> |
2.2.2. 跨域设置-定义CorsWebFilter的Bean
1 | import org.springframework.context.annotation.Bean; |
2.2.3. nacos服务注册 -application.yml
1 | server: |
2.2.4. jwt配置-application.properties
1 | #过滤路由 |
2.2.5. 🌟🌟JwtUtils工具类
1 | package com.runaccepted.jwt.gateway.utils; |
2.2.6. 🌟AuthFilter implements GlobalFilter, Ordered
主要是 Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
其中可以解析出路由过来的请求头信息,并按条件过滤请求,还可以自定义返回结果
为了在免登录期间请求资源问题:token是在2分钟后就失效的,在线失效时间是10分钟,在判断token仅为token过期情况下,向路由传token时,就传刷新token有效时间后的token,不刷新免登录时间,从而保证载荷内容的一致性,也不用2分钟就更新传输过来的token值,当该token的登录时间到期才算真正的token过期
1 | import com.fasterxml.jackson.core.JsonProcessingException; |
2.3. jwt-Client -web项目
2.3.1. Maven依赖
1 | <dependency> |
2.3.2. jwt配置
1 | server.port=9000 |
2.3.3. 🌟🌟JwtUtils工具类
和网关中的唯一不同就是刷新时同时刷新在线时间
1 | package com.runaccepted.jwt.client.utils; |
2.3.4. 🌟ClientController
1 | package com.runaccepted.jwt.client.controller; |
3. 测试 Postman
3.1. http://localhost:9500/jwt-client/login
服务端记录
1 | ERROR 47138 --- [nio-9000-exec-4] c.r.j.c.controller.ClientController : redis key: JWT_USERNAME::1249426830067269633 |
3.2. http://localhost:9500/jwt-client/getInfo
服务端记录
1 | ERROR 42712 --- [ctor-http-nio-3] c.r.jwt.gateway.filter.AuthFilter : 当前路径 /jwt-client/getInfo,是否放行 false |
3.3. http://localhost:9500/jwt-client/logout
服务端记录
1 | ERROR 42712 --- [ctor-http-nio-3] c.r.jwt.gateway.filter.AuthFilter : 放行路径[[/jwt-client/login]],当前路径 /jwt-client/logout,是否放行 false |
3.4. http://localhost:9500/jwt-client/refresh
刷新可以用于在线超时,重新登录
3.5. http://localhost:9500/jwt-client/relogin
用于清除当前服务器中的用户名和token,重新登录进行业务
redis中存在黑名单
1 | 127.0.0.1:6379> keys * |
id对应的token为当前token
1 | 127.0.0.1:6379> hget "JWT_TOKEN" 1249426830067269633 |
此时请求 /jwt-login/login
请求 /jwt-login/getInfo 用原来的token