一、前言

大家好呀,我是summo,之前有自学过Shrio框架,网上一搜就有SpringBoot整合Shrio+ JWT的文章,我是在学习Shrio框架的时候顺带学的JWT。后来我还看见有很多博主专门写文章介绍JWT,说这个东西的优点很多,安全性好、去中心化、方便啥的,我就把JWT也应用在我们自己的系统中了。但最近发现这玩意越来越让我觉得别扭,总感觉哪里不太对劲,重新审查我的登录认证逻辑之后才发现:我不应该用JWT的!

这里我用一句解释不该用的原因,省得浪费大家的时间:我的系统有Redis,而且还用Redis存了JWT,随着系统升级,JWT越来越像普通Token!

明白原理的同学可能心中暗笑,直接跳过看下一篇了,不明白原理的同学,可以看看这个四不像是怎么被我搭出来的。

二、JWT是什么?

看我文章的有很多大神,也有一些小白,所以为了不让小白们看的云里雾里,我还是有必要介绍一些基本原理。JWT是 JSON Web Token 的缩写,可以对JSON对象进行编码(加密),并通过这个编码传递信息。

1. JWT的结构

(1)头部(Header)

头部通常由两部分组成,即令牌的类型(typ)和所使用的算法(alg)。例如,一个头部可能是 {"alg": "HS256", "typ": "JWT"},表示使用 HMAC SHA-256 算法对令牌进行签名。

(2)载荷(Payload)

载荷包含了 JWT 的声明信息,用于描述令牌的相关内容。载荷可以包含标准声明(例如:发行者、主题、过期时间等),也可以包含自定义声明。例如,一个载荷可能是 {"sub": "1234567890", "name": "John Doe", "exp": 1516239022}。

(3)签名(Signature)

签名用于验证令牌的完整性和真实性。签名通常由头部、载荷和密钥一起计算而得。验证者可以使用相同的密钥重新计算签名,并将结果与令牌中的签名进行比较,以确认令牌的真实性。

2. SpringBoot使用JWT

(1)maven引入

<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>

(2)代码示例


import java.util.Date; import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; @Slf4j
public class JWTUtil {
/**
* 过期时间
*/
private static final long EXPIRE_TIME = 60 * 1000; /**
* 校验 token是否正确
*
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
verifier.verify(token);
log.info("token is valid");
return true;
} catch (Exception e) {
log.info("token is invalid{}", e.getMessage());
return false;
}
} /**
* 从 token中获取用户名
*
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
log.error("error:{}", e.getMessage());
return null;
}
} /**
* 生成 token
*
* @param username 用户名
* @param secret 用户的密码
* @return token
*/
public static String sign(String username, String secret) {
try {
username = StringUtils.lowerCase(username);
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
log.error("error:{}", e);
return null;
}
} public static void main(String[] args) {
//对数据进行加密
String token = sign("zhangshan", "123456");
System.out.println(token);
//对数据进行解密
System.out.println(getUsername(token));
}
}

运行一下

JWT还是很简单的,一学就会,很多博主在介绍它的时候都会说它安全、方便、去中心化等等,然后强烈推荐大家使用。但这里我要就要给大家泼冷水了,学是肯定要学的,用就需要看情况了,不能别人说它好,你就无脑用,然后用成一个四不像。至于我为什么说我用来是四不像,接着看!

三、JWT vs Token+Redis

在设计no session系统时,有两种可选方案:JWT与Token+Redis。

1. 原理简介

  • JWT: 生成并发给客户端之后,后台是不用存储,客户端访问时会验证其签名、过期时间等再取出里面的信息(如username),再使用该信息直接查询用户信息完成登录验证。jwt自带签名、过期等校验,后台不用存储,缺陷是一旦下发,服务后台无法拒绝携带该jwt的请求(如踢除用户);

  • Token+Redis: 是自己生成个32位的key,value为用户信息,访问时判断redis里是否有该token,如果有,则加载该用户信息完成登录。服务需要存储下发的每个token及对应的value,维持其过期时间,好处是随时可以删除某个token,阻断该token继续使用。

2. 两种方案的优缺点

(1)去中心化的JWT

优点

  1. 去中心化,便于分布式系统使用
  2. 基本信息可以直接放在token中。 username,nickname,role
  3. 功能权限较少的话,可以直接放在token中。用bit位表示用户所具有的功能权限

缺点

  1. 服务端不能主动让token失效

(2)中心化的Redis+Token

优点

  1. 服务端可以主动让token失效

缺点

  1. 依赖内存或redis存储。
  2. 分布式系统的话,需要redis调用增加了系统复杂性。

光看优缺点的话,JWT优点还比Redis+Token多,在小白时期的我一看:好家伙,JWT这么多优点,用它准没错,看来简历上又可以加上一笔了!

四、我的方案

想法是美好的,但现实是残酷的,为什么我会觉得越来越别扭,先上一张流程图,让大家看看我在业务中是怎么做的,如下:

上面的方案是JWT或者Redis+Token,而我的方案是JWT+Redis。和普通的流程相比,我还加了一个加解密流程,因为我觉得JWT数据格式太明显了,一眼就知道是用的是什么认证方式,容易被篡改。你说这个一点用都没有吗?好像还有那么点用,最起码JWT变得更安全了...

1. 别扭原因

(1)多余的加解密流程

虽然给JWT加一下密提高了安全性,但是导致JWT的自带的过期机制失效了,必须得加上Redis的缓存失效机制,在安全和方便的选项中我选择了安全。

(2)系统对用户的操作频繁

因为我们的系统是一个传统的管理端+C端模式,管理员经常给用户增删权限,刚好命中“服务端不能主动让token失效”这一缺陷,这是最大的原因。

(3)JWT随着系统的升级字符越来越长

JWT存储的信息比较少的时候,还只有一两百个字符,但是字段一多,直接变成“小作文”,我现在看着这一段长长的token,头都大。

(4)单点登录使用JWT太不可控了

实现不了单点登录之后的单点退出功能,即用户在一个系统登出,所有相关系统也自动登出,JWT完全不能满足这个需求。

2. 总结一下

写这篇文章我想肯定会有很多人不服,说我不会用就说人家不好用。诚然,我确实不太会用,不然我也不会用JWT到我的系统中来。我也看了很多介绍JWT的文章,都是说原理和优点,很少有说它的应用场景,知乎上有一篇关于jwt与token+redis,哪种方案更好用?的辩论,很激烈,感兴趣的同学可以去看看。

这个帖子里面的大部分人都认为JWT不是一个必要的组件,甚至有人说任何时候都不应该首要考虑JWT,现在的我深感赞同。里面还有不少老哥推荐使用折中方案,也就是上面我的方案:JWT+Redis,不过我总感觉这种用法很别扭,因为一旦上了Redis那和Redis+Token方案还有什么不同,而且JWT还长的多,白白浪费用户的流量。

里面有个老哥的比喻我觉得非常形象,给大家看下



我觉得这个老哥讲的还挺有道理,JWT本身也只是一个token(有点长的token),非要让它满足所有的需求属实是难为它了。但是如果仅仅只是创造一个新品,如意大利面,就开始疯狂吹嘘它无所不能,给小白一些错误的引导,那就不对了,毕竟喜欢吃意大利面的人也不多。

我不应该用JWT的!的更多相关文章

  1. 看图理解JWT如何用于单点登录

    单点登录是我比较喜欢的一个技术解决方案,一方面他能够提高产品使用的便利性,另一方面他分离了各个应用都需要的登录服务,对性能以及工作量都有好处.自从上次研究过JWT如何应用于会话管理,加之以前的项目中也 ...

  2. JWT实现token-based会话管理

    上文<3种web会话管理的方式>介绍了3种会话管理的方式,其中token-based的方式有必要从实现层面了解一下.本文主要介绍这方面的内容.上文提到token-based的实现目前有一个 ...

  3. 用JWT来保护我们的ASP.NET Core Web API

    在上一篇博客中,自己动手写了一个Middleware来处理API的授权验证,现在就采用另外一种方式来处理这个授权验证的问题,毕竟现在也 有不少开源的东西可以用,今天用的是JWT. 什么是JWT呢?JW ...

  4. Laravel-lumen 配置JWT

    具体步骤参照: [ JWT & Lumen ] 第一步 在项目根目录 执行命令 composer require tymon/jwt-auth第二步 在 bootstrap/app.php 的 ...

  5. .net core Jwt 添加

    Jwt 已经成为跨平台身份验证通用方案,如不了解请关注:https://jwt.io/. 为了和微软其他验证模块有个比较好的衔接,项目中采用了微软开发的jwt组件: System.IdentityMo ...

  6. 多说评论系统API调用和本地身份说明(JWT)

    多说评论系统是一个非常好用的第三方评论插件,聚合了大多数的SNS平台账号登录和分享功能,UI也很不错. 作为网站快速接入评论系统,多说是一个比较好的选择,其也提供了一些实用的API去满足定制化需求. ...

  7. 【JWT】JWT+HA256加密 Token验证

    目录 Token验证 传统的Token验证 JWT+HA256验证 回到顶部 Token验证 最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twi ...

  8. 基于Token的身份验证——JWT

    初次了解JWT,很基础,高手勿喷. 基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session. JWT是啥? JWT就是一个字符串,经过加密处理与校验处理的字符 ...

  9. jwt refresh token

    $app->post('auth/refresh-token', ['middleware' => 'jwt.refresh', function() { try { $old_token ...

  10. JWT【JSON Web Token】 简述

    draft: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html http://tools.ietf.org/html/ ...

随机推荐

  1. IPv6 — 综合组网技术

    目录 文章目录 目录 前文列表 IPv4v6 综合组网技术(转换机制) 双栈策略 隧道策略 前文列表 <IPv6 - 网际协议第 6 版> <IPv6 - 地址格式与寻址模式> ...

  2. java学习之旅(day.20)

    注解和反射 注释comment:给人看 注解annotation:不仅可以给人看,还能给程序看,甚至能被其他程序读取 注解入门 什么是注解 注解的作用: 不是程序本身,可以对程序作出解释(这一点和注释 ...

  3. 腾讯云服务器Ubuntu-配置mysql,nginx,nodejs

    ​趁着618腾讯云服务器的促销活动,笔者买了最低配置的云服务器,用作学习. 接上一篇文章的内容,系统方面选择了Ubuntu,版本是20.04 LTS. 选择Ubuntu的原因,主要是因为Ubuntu一 ...

  4. linux下基于官方源码编译ipopt

    linux下基于官方源码编译ipopt 1.C++依赖项安装升级 由于需要编译c++所以需要安装一系列的依赖: apt-get update apt-get -y upgrade apt instal ...

  5. linux服务器下安装cbc和ipopt求解器【踩坑总结】

    安装CBC求解器 CBC求解器是一个C++库,我们可以通过以下命令在Linux系统中进行安装: sudo apt-get install coinor-cbc 对于Windows操作系统,可以从CBC ...

  6. 算法金 | 详解过拟合和欠拟合!性感妩媚 VS 大杀四方

    大侠幸会,在下全网同名「算法金」 0 基础转 AI 上岸,多个算法赛 Top 「日更万日,让更多人享受智能乐趣」 今天我们来战 过拟合和欠拟合,特别是令江湖侠客闻风丧胆的 过拟合,简称过儿, Emmm ...

  7. Cage 字符串听课笔记

    困困困! KMP 注意到 KMP 的复杂度是均摊的,那么是否可以绕开? 注意到 KMP 实际上一个串的 ACAM,那么考虑可以类似的,在加入一个字符的同时维护 ACAM(考虑 ACAM 的构建过程,前 ...

  8. [踩坑记录] Vue3 customRef 传入对象没有进入set方法

    问题描述 学习Vue3 Ref 相关 API 的时候,遇到了 customRef 这个 API,它可以让我们自定义 ref 的更新的过程 但是使用 customRef 有一个问题就是,如果你传入的是初 ...

  9. Spring扩展——Aware接口

    Aware接口 在Spring中有许多的Aware接口,提供给应用开发者使用,通过Aware接口,我们可以通过set的方式拿到我们需要的bean对象(包括容器中提供的一些对象,ApplicationC ...

  10. 小白也能玩转Git:从入门到实战详细教程

    Git介绍 Git是一种分布式版本控制系统,它广泛应用于软件开发中.通过Git,开发人员可以追踪文件的变化.协作工作.管理代码库等.与集中式版本控制系统(如SVN)不同,Git使每个开发人员都具有完整 ...