
基于SpringSecurity+JWT的微服务鉴权解决方案
用户向服务器发送用户名和密码。 服务器验证用户名和密码,成功后在当前对话(session)中保存相关数据,如用户角色、登录时间等。 服务器返回一个session_id,并将其写入用户的Cookie。 用户随后的每一次请求,都会通过Cookie将session_id发送到服务器。 服务器收到session_id后,通过前期保存的数据确定用户身份。 Cookie存储限制:Cookie的存储空间有限制(通常为4k),无法存储大量数据。 Cookie的有效范围限制:Cookie仅在当前域名下有效,在分布式环境或前后端分离项目中难以使用。 跨端互操作性问题:在app端内嵌网页互相跳转时,原生跳转或内嵌网页跳转会导致与服务端的交互不兼容。 Session数据共享问题:单机环境没有问题,但在高流量情况下,单台服务器无法承接流量,需要进行集群处理,导致Session数据共享问题。 用户登录时,服务端生成JWT token,并保存到Redis(以唯一值如“org+userId”作为key)。 用户携带access_token和refresh_token进行后续请求。 服务端接收JWT token后,先验证token的有效性,再解密token获取关键数据进行处理。 生成JWT token时,使用非对称加密方式(如RSA)进行签名。 解密JWT token时,通过公钥验证签名,获取原始数据。 服务端接收JWT token,调用unJwtToken方法进行解密。 解密后获取username、userId、orgId等信息。 根据业务需求进一步验证用户信息。
发布日期:2021-05-08 05:28:35
浏览次数:21
分类:精选文章
本文共 5633 字,大约阅读时间需要 18 分钟。
背景
公司正在进行一个旧项目权鉴改造。目标是让任意一端登录(web、app、h5)后,可以携带对应的token来请求访问后台服务资源。
达到目的
用户通过任意一端登录后,可以获得对应的token,并利用这个token来访问后台服务资源。
传统认证流程
互联网服务的用户认证一般遵循以下流程:
传统模式的问题
这种认证模式存在以下问题:
Session共享问题的解决方法
针对Session共享问题,可以采取以下解决方法:
Session粘性(仅供了解):
- 通过Session粘性技术,确保同一用户的请求在同一服务器上处理。
- 例如,Nginx负载均衡中使用hash算法进行负载均衡。
Session复制(仅供了解):
- 实现Session复制,使得集群中的各服务器相互保存Session数据。
- Tomcat本身支持基于IP组播的Session复制方式,但可能会带来网络开销和带宽消耗。
统一存储Session数据:
- 集群中的各节点Session数据统一存储到第三方存储设备(如Redis、MySQL)。
- 每个节点获取Session数据时,从集中存储设备中获取,而不是从本地内存获取。
- 这种方式无论是哪个节点新增或修改Session数据,都会反映到集中存储设备。
Cookie Based方法:
- 基于token的方式,不依赖容器本身的Session机制。
- 服务端根据一定算法生成token并发送给客户端。
- 客户端每次请求都携带token,服务端接收token后进行验证和解密,获取关键数据进行处理。
Cookie Based实现方式
JWT(JSON Web Tokens)
JWT是一种基于token的认证方式,服务端不对token进行存储,而是通过签名算法验证并解密token。
传统授权流程
公钥私钥授权流程
JWT验证流程
改造代码落地
旧代码
@GetMapping("/user")public Object list(Principal user, String sso_cookie) { if (verifyCookie) { String ssoCookie = SpringUtil.getSsoCookie(user); if (StringUtil.isBlank(sso_cookie) || !sso_cookie.equals(ssoCookie)) { throw new BusinessException(ResultCode.TOKEN_INVALID); } } return user;}
新代码
private LoginVo packetTokenVo(LoginVo tokenVo, LoginDto dto) { if (StringUtils.isNotBlank(dto.getClient())) { String orgId = dto.getOrgId() == null ? null : dto.getOrgId().toString(); int userType = dto.getUsertype() == null ? 0 : Integer.parseInt(dto.getUsertype()); JwtAccessToken jwtAccessToken = new JwtAccessToken(dto.getUserName(), orgId, dto.getIdCard(), 1, dto.getClient(), userType); String accessTokenStr = JwtUtil.jwtAccessTokenHM256(jwtAccessToken, JwtUtil.SECRET); JwtRefreshToken jwtRefreshToken = new JwtRefreshToken(dto.getUserName(), dto.getIdCard(), 1, dto.getClient(), userType, orgId); String refreshTokenStr = JwtUtil.jwtRefreshTokenHM256(jwtRefreshToken, JwtUtil.SECRET); cacheAccessTokenAndRefreshToken(atkey, accessTokenStr, rtkey, refreshTokenStr); tokenVo.setAccessToken(accessTokenStr); tokenVo.setRefreshToken(refreshTokenStr); } return tokenVo;}
@GetMapping("/validate")public Object validate(String jwt, String sso_cookie) { if (verifyCookie) { JwtToken rawJwtToken = JwtUtil.unJwtToken(jwt); if (rawJwtToken == null || StringUtils.isEmpty(rawJwtToken.getClientId())) { throw new BusinessException(ResultCode.TOKEN_INVALID); } String accessKeyStr = TokenKeyUtil.getAccessTokenKey(verifyJwtToken.getClientId(), verifyJwtToken.getAppType(), verifyJwtToken.getUserId(), verifyJwtToken.getOrgId()); Long validation = (Long) redisTemplate.execute(validateAccessTokenScript, Arrays.asList(accessKeyStr), jwt); if (!accessKeyStr.equals(jwt)) { throw new BusinessException(ResultCode.TOKEN_INVALID); } } return jwt;}
加密解密代码
生成JWT token
public static String jwtAccessTokenHM256(JwtAccessToken jwtAccessToken, String secret) { try { Algorithm algorithm = Algorithm.HMAC256(secret); String token = JWT.create() .withClaim("username", jwtAccessToken.getUsername()) .withClaim("userId", jwtAccessToken.getUserId()) .withClaim("orgId", jwtAccessToken.getOrgId()) .withClaim("appType", jwtAccessToken.getAppType()) .withClaim("clientId", jwtAccessToken.getClientId()) .withClaim("tokenType", jwtAccessToken.getTokenType()) .withClaim("userType", jwtAccessToken.getUserType()) .withIssuer(ISSUER) .withIssuedAt(new Date()) .sign(algorithm); return token; } catch (Exception exception) { logger.error("jwt处理异常:", exception); throw new RuntimeException(); }}
解密JWT token
public static JwtToken unJwtToken(String token) { try { DecodedJWT jwt = JWT.decode(token); MapclaimMap = jwt.getClaims(); String username = claimMap.get("username").asString(); String userId = claimMap.get("userId").asString(); String orgId = claimMap.get("orgId").asString(); int appType = claimMap.get("appType").asInt(); String clientId = claimMap.get("clientId").asString(); String tokenType = claimMap.get("tokenType").asString(); int userType = claimMap.get("userType").asInt(); JwtToken jwtToken = null; if (TokenType.ACCESS_TOKEN.equals(tokenType)) { jwtToken = new JwtAccessToken(username, orgId, userId, appType, clientId, userType); } else if (TokenType.REFRESH_TOKEN.equals(tokenType)) { jwtToken = new JwtRefreshToken(username, userId, appType, clientId, userType, orgId); } return jwtToken; } catch (Exception e) { logger.error("jwt处理异常:", e); throw new BusinessException("jwt解析错误"); }}
总结
JWT实际上定义了一种数据加密与签名的规范,可以实现单点登录和数据传输及验签功能。该方案无法传递敏感信息(如密码),因为JWT中的部分内容可以解密,但不能修改。
发表评论
最新留言
路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月05日 18时31分57秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Java对象转JSON时如何动态的增删改查属性
2019-03-06
Python 面向对象进阶
2019-03-06
Linux常用统计命令之wc
2019-03-06
Git安装及使用以及连接GitHub方法详解
2019-03-06
docker容器与虚拟机的区别
2019-03-06
shell脚本里使用echo输出颜色
2019-03-06
Python2跟Python3的区别
2019-03-06
并发编程——IO模型详解
2019-03-06
Java之封装,继承,多态
2019-03-06
wait()与notify()
2019-03-06
使用js打印时去除页眉页脚
2019-03-06
Spring security OAuth2.0认证授权学习第二天(基础概念-RBAC)
2019-03-06
ORA-00904: "FILED_TYPE": 标识符无效
2019-03-06
数据仓库系列之维度建模
2019-03-06
Scala教程之:函数式的Scala
2019-03-06
java中DelayQueue的使用
2019-03-06
线程stop和Interrupt
2019-03-06
Android中定时执行任务的3种实现方法
2019-03-06
nodejs中npm常用命令
2019-03-06