原文网址:JWT--使用/教程/实例_IT利刃出鞘的博客-CSDN博客

简介

说明

        本文介绍JWT知识。包括:什么是JWT、JWT的消息组成、JWT实战(测试java-jwt和jjwt两个工具类)。

JWT是常用的TOKEN工具,本文介绍JWT知识。包括:什么是JWT、JWT的消息组成、JWT实战(java-jwt和jjwt两种客户端都测试一下)。

官网

JSON Web Tokens - jwt.io      (可生成/解析 Token)

JWT简介

什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。JWT将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证;应用场景如用户登录。

JWT可通过URL、POST参数或HTTP header发送,数据量小;负载中可包含用户信息,避免多次查询数据库。

JWT定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

JWT请求流程

  1. 用户发出post请求(包含账户和密码);
  2. 服务器使用私钥创建一个jwt;
  3. 服务器返回这个jwt给浏览器;
  4. 浏览器将该jwt串在请求头中向服务器发送请求;
  5. 服务器验证该jwt;
  6. 返回响应的资源给浏览器。

Cookie+Session与JWT对比

Cookie+Session

用户登录认证中,因为http是无状态的,所以采用session方式。用户登录成功,服务端会保存一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
        Cookie+session这种模式通常是保存在服务器内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。

JWT

        只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。简单便捷,无需通过Redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验。

JWT优点

  1. 占资源少

    1. 不在服务端保存信息。
  2. 扩展性
    1. 分布式中,Session需要做多机数据共享,通常存在数据库或者Redis里面。而JWT不需要。
  3. 跨语言
    1. Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;

JWT缺点

  1. 无法废弃已颁布的令牌

    1. 一旦签发一个JWT,在到期之前就会始终有效,无法中途废弃。例如在payload中存储了一些信息,当信息需要更新时,则重新签发一个JWT,但由于旧的jwt还没过期,拿着这个旧的JWT依旧可以登录,那登录后服务端从JWT中拿到的信息就是过时的。

      1. 解决方案:服务端部署额外的逻辑,例如:设置黑名单,一旦签发了新的JWT,旧的就加入黑名单(比如存到Redis里面),避免被再次使用。(违背JWT初衷)
  2. 过期
    1. Cookie续签方案一般都是框架自带的,如:Session有效期30分钟,若30分钟内有访问,有效期刷新至30分钟。对于JWT,改变JWT的有效时间,就要签发新的JWT。

      1. 解决方案1::每次请求刷新JWT。即每个HTTP请求都返回一个新的JWT。这个方法不仅暴力不优雅,而且每次请求都要做JWT的加密解密,会带来性能问题。
      2. 解决方案2:在Redis中单独为每个JWT设置过期时间,每次访问时刷新JWT的过期时间。引入 Redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了JWT的初衷。而且这个方案和Session都差不多了。

JWT消息构成

一个token分3部分,按顺序为:头部(header)、载荷(payload)、签证(signature)。三部分之间用.号做分隔。例如(编码后的格式):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

上边解码后结果如下图:

header

简介

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

包含两个信息:token类型和采用的加密算法。

token类型:本处是jwt 
加密的算法:通常直接使用 HMAC SHA256

加密算法

加密算法是单向函数散列算法,常见的有:MD5、SHA、HAMC。

  • MD5(message-digest algorithm 5)信息-摘要 算法

    • 广泛用于加密和解密技术,常用于文件校验。不管文件多大,经过MD5后都能生成唯一的MD5值
  • SHA (Secure Hash Algorithm)安全散列算法
    • 数字签名等密码学应用中重要的工具,安全性高于MD5
  • HMAC (Hash Message Authentication Code)散列消息鉴别码
    • 基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。常用于接口签名验证

JWT验证和签名使用的算法列表如下:

JWS 算法名称
HS256 HMAC256
HS384 HMAC384
HS512 HMAC512
RS256 RSA256
RS384 RSA384
RS512 RSA512
ES256 ECDSA256
ES384 ECDSA384
ES512 ECDSA512

payload

{"sub": "1234567890","name": "John Doe","iat": 1516239022
}

载荷就是存放有效信息的地方。包含三个部分 1.标准中注册的声明 2.公共的声明 3.私有的声明。

不应该在JWT的payload存储敏感信息,因为该部分是客户端可解密的部分。

标准中注册的声明 :

含义
iss Issuer。 jwt签发者
sub Subject。用户。一般是用户名
aud Audience。接收jwt的一方。一般写用户id
exp Expiration Time。过期时间戳(必须要大于签发时间)
nbf Not Before。生效时间戳。该时间之前,该jwt都是不可用的。
iat  Issued At。签发时间戳。
jti 

JWT ID。JWT的唯一标识。

主要用来作为一次性token,从而回避重放攻击。

公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息

私有的声明

提供者和消费者所共同定义的声明

signature

简介

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),your-256-bit-secret)

Signature 部分是对前两部分的签名,防止数据篡改。

1.指定一个密钥(secret)。这个secret保存在服务端,服务端根据这个密钥生成token和进行验证,要保护好,不能泄露给用户。

2.使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名(Signature)。

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),
your-256-bit-secret
)

3.把 Header(base64加密后的)、Payload(base64加密后的)、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,返回给用户。

实战

java-jwt的github:https://github.com/auth0/java-jwt
jjwt的github:https://github.com/jwtk/jjwt

本项目网址:https://gitee.com/shapeless/demo/tree/master/demo_JWT

创建项目

引入依赖

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.18.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

说明:这两个引入一个即可。本处为了测试这两个库的用法,所以都引入了。

token实现库

token在java中共有6个实现库,见:JSON Web Tokens - jwt.io   其使用对比见:各类JWT库(java)的使用与评价 | MONKEYK.博客

下边介绍常用的两个:

  1. java-jwt

    1. 生成id_token与 校验(verify)id_token时都需要 公钥(public key)与私钥(private key),个人感觉是一不足(实际上在校验时只需要public key即可)。
  2. jjwt
    1. 小巧够用, 但对JWT的一些细节包装不够, 比如 Claims (只提供获取header,body)

application.yml

server:port: 8080
spring:application:name: springboot-jwt
config:jwt:# 密钥secret: abcd1234# token过期时间(5分钟)。单位:秒.expire: 300

工具类

本处会测试两个库:java-jwt和jjwt,所以,写一个接口,两个实现类。

接口

package com.example.demo.util;public interface IJwtUtil {String createToken(String userId);boolean verifyToken(String token);String getUserIdByToken(String token);String getUserNameByToken(String token);
}

java-jwt实现类

package com.example.demo.util.impl;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.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.demo.util.IJwtUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;@Component("javaJwtUtil")
public class JavaJwtUtil implements IJwtUtil {// 过期时间@Value("${config.jwt.expire}")private Long EXPIRE_TIME;// 密钥@Value("${config.jwt.secret}")private String SECRET;// 创建Token@Overridepublic String createToken(String userId) {try {Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME * 1000);Algorithm algorithm = Algorithm.HMAC256(SECRET);return JWT.create()// 自定义私有的payload的key-value。比如:.withClaim("userName", "Tony")// .withClaim("key1", "value1").withAudience(userId)  // 将 user id 保存到 token 里面.withExpiresAt(date)   // date之后,token过期.sign(algorithm);      // token 的密钥} catch (Exception e) {return null;}}// 校验tokenpublic boolean verifyToken(String token) {try {Algorithm algorithm = Algorithm.HMAC256(SECRET);JWTVerifier verifier = JWT.require(algorithm)// .withIssuer("auth0")// .withClaim("username", username).build();DecodedJWT jwt = verifier.verify(token);return true;} catch (JWTVerificationException exception) {// throw new RuntimeException("token 无效,请重新获取");return false;}}// 根据token获取userId@Overridepublic String getUserIdByToken(String token) {try {String userId = JWT.decode(token).getAudience().get(0);return userId;} catch (JWTDecodeException e) {return null;}}// 根据token获取userName@Overridepublic String getUserNameByToken(String token) {try {String userName = JWT.decode(token).getSubject();return userName;} catch (JWTDecodeException e) {return null;}}
}

jjwt实现类

package com.example.demo.util.impl;import com.example.demo.util.IJwtUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;@Component("jJwtUtil")
public class JJwtUtil implements IJwtUtil {//过期时间@Value("${config.jwt.expire}")private Long EXPIRE_TIME;//密钥@Value("${config.jwt.secret}")private String SECRET;// 生成token(通过用户Id)@Overridepublic String createToken (String userId){Date nowDate = new Date();System.out.println(nowDate);Date expireDate = new Date(nowDate.getTime() + EXPIRE_TIME * 1000);// 可添加自定义私有的payload的key-value。若与已有的重名会覆盖// Map<String, String> claims = new HashMap();// claims.put("key1", "value1");return Jwts.builder().setAudience(userId).setIssuedAt(nowDate).setExpiration(expireDate)// .setClaims(claims).signWith(SignatureAlgorithm.HS512, generalKey()).compact();}@Overridepublic boolean verifyToken(String token) {try {Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(token);return true;}catch (Exception e){// e.printStackTrace();return false;}}// 从token中获取用户名@Overridepublic String getUserNameByToken(String token) {return getTokenClaim(token).getSubject();}// 从token中获取用户id@Overridepublic String getUserIdByToken(String token) {return getTokenClaim(token).getAudience();}// 获取token中注册信息private Claims getTokenClaim (String token) {try {return Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(token).getBody();}catch (Exception e){// e.printStackTrace();return null;}}// 将secret加密private SecretKey generalKey(){String stringKey = SECRET;byte[] encodedKey = Base64.encodeBase64(stringKey);return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");}
}

拦截器

package com.example.demo.interceptor;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getJwtInterceptor());}@BeanJwtInterceptor getJwtInterceptor() {return new JwtInterceptor();}
}
package com.example.demo.interceptor;import com.example.demo.util.IJwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;@Slf4j
public class JwtInterceptor implements HandlerInterceptor {@Autowired@Qualifier("jJwtUtil")IJwtUtil iJwtUtil;List<String> whiteList = Arrays.asList("/auth/login","/error");@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {// 如果不是映射到方法直接通过if (!(handler instanceof HandlerMethod)) {return true;}//放过不需要验证的页面。String uri = request.getRequestURI();if (whiteList.contains(uri)) {return true;}// 头部和参数都查看一下是否有tokenString token = request.getHeader("token");if (StringUtils.isEmpty(token)) {token = request.getParameter("token");if (StringUtils.isEmpty(token)) {throw new RuntimeException("token是空的");}}if (!iJwtUtil.verifyToken(token)) {log.error("token无效");return false;}String userId = iJwtUtil.getUserIdByToken(token);log.info("userId:" + userId);String userName = iJwtUtil.getUserNameByToken(token);log.info("userName:" + userName);return true;}
}

控制器

package com.example.demo.controller;import com.example.demo.util.IJwtUtil;
import com.example.demo.util.impl.JavaJwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/auth")
public class AuthController {@Autowired@Qualifier("jJwtUtil")IJwtUtil iJwtUtil;@RequestMapping("/login")public String login() {// 验证userName,password和数据库中是否一致,如不一致,直接返回失败// 通过userName,password从数据库中获取userIdString userId = 5 + "";String token = iJwtUtil.createToken(userId);System.out.println("token:" + token);return token;}//需要token验证@RequestMapping("/info")public String info() {return  "验证通过";}
}

测试

前端访问:http://localhost:8080/auth/login

后端/前端结果:

eyJhbGciOiJIUzUxMiJ9.eyJhdWQiOiI1IiwiaWF0IjoxNTk5NDAyNzI0LCJleHAiOjE1OTk0MDMwMjR9.5yGeY2cf84s9GmXWGPb0w67J5G7AVlKXz7QtErA0xSzpw_3zhpLowEeDUYdMWTy4TxSTJfEL-K8CLzosFJECUQ

前端访问:http://localhost:8080/auth/info(不带token)

后端结果:

java.lang.RuntimeException: token是空的at com.example.demo.interceptor.JwtInterceptor.preHandle(JwtInterceptor.java:46) ~[main/:na]

前端结果

{"timestamp": "2020-09-06T14:58:32.073+00:00","status": 500,"error": "Internal Server Error","message": "","path": "/auth/info"
}

前端访问:http://localhost:8080/auth/info(token作为header后者参数)

后端结果

2020-09-06 23:00:27.832  INFO 6748 --- [nio-8080-exec-7] c.e.demo.interceptor.JwtInterceptor      : userId:5
2020-09-06 23:00:27.832  INFO 6748 --- [nio-8080-exec-7] c.e.demo.interceptor.JwtInterceptor      : userName:null

前端结果

验证通过

其他网址

SpringBoot2.0 - 集成JWT实现token验证_麦叔-CSDN博客

SpringBoot整合JWT_AkiraNicky的博客-CSDN博客
Spring Boot认证:整合Jwt - YoungDeng - 博客园

JWT--使用/教程/实例相关推荐

  1. adc 接收cube_官方的stm32cube软件教程实例ADC操作代码(官方自带的,可以无视

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 官方的stm32cube软件教程实例ADC操作代码(官方自带的,可以无视),看不懂怎么用的可以等本贴吧更新图片教程,现在就是凑帖子数量,完成转职的,请谅解 ...

  2. Java-Runoob-高级教程-实例-数组:01. Java 实例 – 数组排序及元素查找

    ylbtech-Java-Runoob-高级教程-实例-数组:01. Java 实例 – 数组排序及元素查找 1.返回顶部 1. Java 实例 - 数组排序及元素查找  Java 实例 以下实例演示 ...

  3. Java-Runoob-高级教程-实例-字符串:13. Java 实例 - 字符串格式化

    ylbtech-Java-Runoob-高级教程-实例-字符串:13. Java 实例 - 字符串格式化 1.返回顶部 1. Java 实例 - 字符串格式化  Java 实例 以下实例演示了通过 f ...

  4. 单片机独立式按键c语言程序,(原创)51单片机C语言程序设计--速学教程实例(入门篇)之独立按键(查询)...

    (原创)51单片机C语言程序设计--速学教程实例(入门篇)之独立按键(查询) /************************************************************ ...

  5. Flex 布局教程实例

    Flex 布局教程实例 一.Flex 布局是什么? Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性. 任何一个容器都可以指定为 F ...

  6. Java-Runoob-高级教程-实例-数组:10. Java 实例 – 查找数组中的重复元素-un

    ylbtech-Java-Runoob-高级教程-实例-数组:10. Java 实例 – 查找数组中的重复元素 1.返回顶部 1. Java 实例 - 查找数组中的重复元素  Java 实例 以下实例 ...

  7. android 用户界面教程实例汇总

    1.android用户界面之WebView教程实例汇总 http://www.apkbus.com/android-51718-1-1.html 2.android用户界面之Notification教 ...

  8. Java-Runoob-高级教程-实例-方法:11. Java 实例 – enum 和 switch 语句使用

    ylbtech-Java-Runoob-高级教程-实例-方法:11. Java 实例 – enum 和 switch 语句使用 1.返回顶部 1. Java 实例 - enum 和 switch 语句 ...

  9. android用户界面之AlarmManager教程实例汇

    qianqianlianmeng android用户界面之AlarmManager教程实例汇 AlarmManager使用教程 1. 如何利用AlarmManager机制在一定时间后启动某Activi ...

最新文章

  1. ELK6.0已取消filebeat配置document_type
  2. TensorFlow中文社区论坛 发布上线!
  3. 解决了无法显示验证码的问题
  4. 【图解机器学习】人人都能懂的算法原理
  5. sklearn自学指南(part52)--潜在狄利克雷分配(LDA)
  6. 格式化字符串漏洞利用 五、爆破
  7. 图解TCPIP-IP 网际协议-IP包
  8. maven spring hibernate shiro
  9. Linux 命令(37)—— free 命令
  10. Word Frequency(Leetcode192)
  11. 织梦列表页list标签调用支持flag属性方法
  12. 【Python从入门到精通】(一)就简单看看Python吧
  13. 熔断机制什么意思_什么是熔断机制 熔断机制是什么意思
  14. Java工程师薪资究竟有多高?
  15. 暗原色先验图像去雾算法研究_先验算法
  16. html怎么制作气泡,制作CSS气泡框
  17. java+mysql crm客户关系区块链毕业管理系统设计与论文
  18. mysql 计算中位数_【转】MySQL如何计算中位数
  19. Fresco图片加载框架的介绍,相关开源库以及工具类的封装
  20. 在 Microsoft Edge 浏览器上安装 Vue 项目调试扩展插件 Vue-Devtools

热门文章

  1. 机器学习中的数学——Adam(Adaptive Moments)
  2. stm32F103上基于FreeRTOS系统的亮度可调小台灯
  3. 时间序列之格兰杰因果关系检验(4)
  4. 零基础学习嵌入式开发难吗?嵌入式开发需要学习什么
  5. 对python的认识作文500字_关于启示的作文500字
  6. 动态数组的使用之char *res=new char(strlen(src)+1)
  7. 关注天气:免费的短信天气预报
  8. layout_constraintWidth_percent in java
  9. 【转载】裸眼识别二维码
  10. 第一章、Ansible的详细介绍与安装