一、JWT

1、JWT介绍

JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用户登录。在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保存一个session,服务端会返回给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题。虽然目前存在使用Redis进行Session共享的机制,但是随着用户量和访问量的增加,Redis中保存的数据会越来越多,开销就会越来越大,多服务间的耦合性也会越来越大,Redis中的数据也很难进行管理,例如当Redis集群服务器出现Down机的情况下,整个业务系统随之将变为不可用的状态。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。

2、JWT的结构解析


第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)

  • header
    jwt的头部承载两部分信息:
    1.声明类型,这里是jwt
    2.声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{"typ": "JWT",
"alg": "HS256"
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • playload
    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:标准中注册的声明、公共的声明、私有的声明。
  1. 标准中注册的声明 (建议但不强制使用) :
    iss: jwt签发者
    sub: jwt所面向的用户
    aud: 接收jwt的一方
    exp: jwt的过期时间,这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前,该jwt都是不可用的.
    iat: jwt的签发时间
    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  2. 公共的声明 :
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
  3. 私有的声明 :
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    定义一个payload:
{"uid":"e12a34b56c78d9e0f",
"name":"ramostear",
"role":"admin"
}

然后将其进行base64加密,得到Jwt的第二部分:

eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9
  • signature
    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header (base64后的)、
    payload (base64后的)、secret。
    这个部分需要base64加密后的header和base64加密后的payload使用。连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:
49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY

密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。

下图为一个JWT生成流程示例:

3、jwt认证流程

在身份验证中,当用户成功登录系统时,授权服务器将会把 JSON Web Token 返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。如果凭证有效,将放行请求;若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。以访问 API 资源为例,下图显示了获取并使用 JWT 的基本流程:

4、集成和使用说明

添加依赖:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.version}</version>
</dependency>

生成以及解析 jwt token方法:

/*** 生成token方法* @param claims map对象,可传递需要携带的参数* @return*/
public String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.forName(alg), secret).compact();
}
/*** 获得token内的内容* @param token* @return*/
public Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {claims = new DefaultClaims();log.warn("{}", e.getMessage(), e);}return claims;
}

处理 jwt token 过期问题:

/*** 生成过期时间* @return date*/
public Date generateExpirationDate() {return new Date(System.currentTimeMillis() + expiration * MILLISECOND);
}
/*** 判断token是否失效* @param token* @return*/
public Boolean isTokenExpired(String token) {final Date expirationDate = getExpirationDateFromToken(token);if (expirationDate == null) {return false;}return expirationDate.before(new Date());
}
/*** 获取过期时间* @param token* @return date*/
public Date getExpirationDateFromToken(String token) {Date expirationDate;try {//获得token内的内容final Claims claims = getClaimsFromToken(token);expirationDate = claims.getExpiration();} catch (Exception e) {expirationDate = null;log.warn("{}", e.getMessage(), e);}return expirationDate;
}

jwt token定期刷新处理方法:

/*** 刷新token* * @param token jwt的token* @return 刷新后的token*/
public String refreshToken(String token) {String refreshedToken;try {final Claims claims = getClaimsFromToken(token);claims.put("created", new Date());refreshedToken = generateToken(claims);} catch (Exception e) {refreshedToken = null;log.warn("{}", e.getMessage(), e);}return refreshedToken;
}

二、JWT的利弊以及并发处理

1、 使用 JWT 的优势
使用 JSON Web Token 保护应用安全,你至少可以获得以下几个优势:

  • 更少的数据库连接:因其基于算法来实现身份认证,在使用 JWT 时查询数据的次数更少(更少的数据连接不等于不连接数据库),可以获得更快的系统响应时间。构建更简单:如果你的应用程序本身是无状态的,那么选择 JWT 可以加快系统构建过程。

  • 跨服务调用:你可以构建一个认证中心来处理用户身份认证和发放签名的工作,其他应用服务在后续的用户请求中不需要(理论上)在询问认证中心,可使用自有的公钥对用户签名进行验证。

  • 无状态:你不需要向传统的 Web 应用那样将用户状态保存于 Session 中。

2、使用 JWT 的弊端

  • 严重依赖于秘钥:JWT 的生成与解析过程都需要依赖于秘钥(Secret),且都以硬编码的方式存在于系统中(也有放在外部配置文件中的)。如果秘钥不小心泄露,系统的安全性将收到威胁。

  • 服务端无法管理客户端的信息:如果用户身份发生异常(信息泄露,或者被攻击),服务端很难向操作 Session 那样主动将异常用户进行隔离。

  • 服务端无法主动推送消息:服务端由于是无状态的,他将无法使用像 Session 那样的方式推送消息到客户端,例如过期时间将至,服务端无法主动为用户续约,需要客户端向服务端发起续约请求。

  • 冗余的数据开销:一个 JWT 签名的大小要远比一个 Session ID 长很多,如果你对有效载荷(payload)中的数据不做有效控制,其长度会成几何倍数增长,且在每一次请求时都需要负担额外的网络开销。

  • JSON Web Token 很流行,但是它相比于 Session,OIDC(OpenId Connect)等技术还比较新,支持 JSON Web Token 的库还比较少,而且 JWT 也并非比传统 Session 更安全,他们都没有解决 CSRF 和 XSS 的问题。因此,在决定使用 JWT 前,你需要仔细考虑其利弊。

JWT(Json Web Token)如何解决并发问题的思考

由于JWT这种形式的请求属于无状态的,请求过程中需要等到token过期后采取刷新,在HTTP请求并发这块并没有很好的解决办法;

当服务端在检查到请求的令牌过期之后,会刷新Token重新颁发令牌,并且再次做登录操作,流程上没什么问题,但在页面加载后倘若同一个页面中有多个请求几乎同一时间发起,每一个请求都携带原始令牌,在这样的设计下,就有可能出现在第一个请求到达后刷新了Token,并更改了缓存中的refreshToken的时间戳,以至于剩余请求校验时发现时间戳不一致导致验证失败而在日志中多次打印出当前Token已经失效的log。同时发起的请求越多,log中的异常也就会越多。虽然第一个请求已经刷新了Token,但是其余的请求是失败的,页面中的数据并不完整,显然这是不正常的,那该如何解决呢?

当然实现的方式可以有多种,如我们现在Token过期后刷新再加synchronized生成Token策略,或者前端定时去调用服务端API刷新Token,再如这里即将采用的Token在有效期内定时更新的方式。

在采用有效期内定时刷新的逻辑之前,引用一段介绍:

一个好的模式是在它过期之前刷新令牌。将令牌过期时间设置为一周,并在每次用户打开
Web应用程序并每隔一小时刷新令牌。如果用户超过一周没有打开过应用程序,那他们就
需要再次登录,这是可接受的Web应用程序UX(用户体验)。要刷新令牌,API需要一个新
的端点,它接收一个有效的,没有过期的JWT,并返回与新的到期字段相同的签名的
JWT。然后Web应用程序会将令牌存储在某处。

避免并发情况下token失效问题,可以采用以下方案处理:

  1. Redis锁机制限制并发请求
    为避免多个请求同一时间分别生成不同的Token,我们引入redis锁机制。即我们的目的是同一个用户同一时间的不同请求,只允许获得锁的请求进行令牌刷新,其他的请求因为是在令牌有效期内直接放行。
  2. 加入Token验证通过后定时刷新Token的逻辑
    将原来设计的Token到期后刷新,重新修改为Token在有效期内刷新,使得Token一旦到期,则直接跳转到登录页,保证了同一个用户,并发的请求只会更换一次令牌

JWT — JWT原理解析及实际使用相关推荐

  1. aspnet core 2.1中使用jwt从原理到精通二

    在aspnet core中,自定义jwt管道验证 有了上一节的内容作为基础,那这点也是非常容易的,关键点在中间件,只是把上一级在测试类中的自定义验证放到中间件中来即可, 不过需要注意:中间件 的位置很 ...

  2. JWT认证原理、整合springboot实战应用

    JWT认证原理.整合springboot实战应用 1.什么是JWT 2.JWT能做什么 3.与传统的session认证做对比 4.JWT结构 5.JWT的封装方法 1.什么是JWT JWT(Json ...

  3. 浅析JWT| JWT是啥子,Java构建JWT

    小声bbb 说到系统的安全识别,记得自己第一个项目,用的就是session,用户登录进来以后,给他session标记登录,记录id进去,轻轻松松,设计的操作也很简单,类似操作HashMap. 这大概也 ...

  4. Spark Shuffle原理解析

    Spark Shuffle原理解析 一:到底什么是Shuffle? Shuffle中文翻译为"洗牌",需要Shuffle的关键性原因是某种具有共同特征的数据需要最终汇聚到一个计算节 ...

  5. 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)

    2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六)  中, 介绍了 ...

  6. Tomcat 架构原理解析到架构设计借鉴

    ‍ 点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 Tomcat 架构原理解析到架构设计借鉴 Tomcat 发展这 ...

  7. 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  8. CSS实现元素居中原理解析

    原文:CSS实现元素居中原理解析 在 CSS 中要设置元素水平垂直居中是一个非常常见的需求了.但就是这样一个从理论上来看似乎实现起来极其简单的,在实践中,它往往难住了很多人. 让元素水平居中相对比较简 ...

  9. 秋色园QBlog技术原理解析:Web之页面处理-内容填充(八)

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

最新文章

  1. TCP连接之报文首部
  2. JS中 window.location.search的作用
  3. P3345-[ZJOI2015]幻想乡战略游戏【点分树,RMQ】
  4. 软件工程第八次作业——例行报告
  5. 95-180-055-源码-Watermark-AutoMaticWatermarkContext
  6. JAVA中修改顺序表中的元素_在Java中修改列表的每个项目
  7. Apache启动失败
  8. 遍历所有点的最短路径python_Python:如何优化所有可能的最短路径的计数?
  9. 创意油墨飞溅效果的绿树矢量素材
  10. java jre 1.8_安装java1.8和配置环境变量
  11. 平台打包整合成exe安装部署(java+mysql+tomcat+war)
  12. LitePal数据存储
  13. java pdf添加图片_Java 给 PDF 设置背景图片
  14. 二叉树的顺序存储和三种遍历(代码)
  15. Downward paths
  16. 这样充满青春活力的微信公众号图文排版,你见过吗?
  17. 天猫618红包口令怎么获取?天猫618红包使用条件有哪些?
  18. 居家学习:新冠肺炎疫情下中国高校基于直播的远程教育体验的混合方法分析
  19. 报错:HTTP 401 Unauthorized
  20. jffs2文件系统制作与移植(二)

热门文章

  1. 【本科生科研入门】如何整理个人大学生涯的成果?
  2. group by 分组后 再对所有的数据求和
  3. GTP(GPRS Tunnelling Protocol)协议http://blog.csdn.net/stephen_yin/article/details/6951237
  4. Day-8 bootstrap 徽章、进度条、分页、列表组
  5. C++:GCC编译:GCC编译C++程序分步流程
  6. uniapp中修改uni.showModal弹框content内容样式
  7. RestTemplete
  8. Robotframework自动化测试框架
  9. 【042】904. 水果成篮[滑动窗口]
  10. golang 编译错误:unknown revision xxx