JWT--使用/教程/实例
原文网址: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请求流程
- 用户发出post请求(包含账户和密码);
- 服务器使用私钥创建一个jwt;
- 服务器返回这个jwt给浏览器;
- 浏览器将该jwt串在请求头中向服务器发送请求;
- 服务器验证该jwt;
- 返回响应的资源给浏览器。
Cookie+Session与JWT对比
Cookie+Session
用户登录认证中,因为http是无状态的,所以采用session方式。用户登录成功,服务端会保存一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
Cookie+session这种模式通常是保存在服务器内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。
JWT
只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。简单便捷,无需通过Redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验。
JWT优点
- 占资源少
- 不在服务端保存信息。
- 扩展性
- 分布式中,Session需要做多机数据共享,通常存在数据库或者Redis里面。而JWT不需要。
- 跨语言
- Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;
JWT缺点
- 无法废弃已颁布的令牌
- 一旦签发一个JWT,在到期之前就会始终有效,无法中途废弃。例如在payload中存储了一些信息,当信息需要更新时,则重新签发一个JWT,但由于旧的jwt还没过期,拿着这个旧的JWT依旧可以登录,那登录后服务端从JWT中拿到的信息就是过时的。
- 解决方案:服务端部署额外的逻辑,例如:设置黑名单,一旦签发了新的JWT,旧的就加入黑名单(比如存到Redis里面),避免被再次使用。(违背JWT初衷)
- 一旦签发一个JWT,在到期之前就会始终有效,无法中途废弃。例如在payload中存储了一些信息,当信息需要更新时,则重新签发一个JWT,但由于旧的jwt还没过期,拿着这个旧的JWT依旧可以登录,那登录后服务端从JWT中拿到的信息就是过时的。
- 过期
- Cookie续签方案一般都是框架自带的,如:Session有效期30分钟,若30分钟内有访问,有效期刷新至30分钟。对于JWT,改变JWT的有效时间,就要签发新的JWT。
- 解决方案1::每次请求刷新JWT。即每个HTTP请求都返回一个新的JWT。这个方法不仅暴力不优雅,而且每次请求都要做JWT的加密解密,会带来性能问题。
- 解决方案2:在Redis中单独为每个JWT设置过期时间,每次访问时刷新JWT的过期时间。引入 Redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了JWT的初衷。而且这个方案和Session都差不多了。
- Cookie续签方案一般都是框架自带的,如:Session有效期30分钟,若30分钟内有访问,有效期刷新至30分钟。对于JWT,改变JWT的有效时间,就要签发新的JWT。
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.博客
下边介绍常用的两个:
- java-jwt
- 生成id_token与 校验(verify)id_token时都需要 公钥(public key)与私钥(private key),个人感觉是一不足(实际上在校验时只需要public key即可)。
- jjwt
- 小巧够用, 但对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--使用/教程/实例相关推荐
- adc 接收cube_官方的stm32cube软件教程实例ADC操作代码(官方自带的,可以无视
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 官方的stm32cube软件教程实例ADC操作代码(官方自带的,可以无视),看不懂怎么用的可以等本贴吧更新图片教程,现在就是凑帖子数量,完成转职的,请谅解 ...
- Java-Runoob-高级教程-实例-数组:01. Java 实例 – 数组排序及元素查找
ylbtech-Java-Runoob-高级教程-实例-数组:01. Java 实例 – 数组排序及元素查找 1.返回顶部 1. Java 实例 - 数组排序及元素查找 Java 实例 以下实例演示 ...
- Java-Runoob-高级教程-实例-字符串:13. Java 实例 - 字符串格式化
ylbtech-Java-Runoob-高级教程-实例-字符串:13. Java 实例 - 字符串格式化 1.返回顶部 1. Java 实例 - 字符串格式化 Java 实例 以下实例演示了通过 f ...
- 单片机独立式按键c语言程序,(原创)51单片机C语言程序设计--速学教程实例(入门篇)之独立按键(查询)...
(原创)51单片机C语言程序设计--速学教程实例(入门篇)之独立按键(查询) /************************************************************ ...
- Flex 布局教程实例
Flex 布局教程实例 一.Flex 布局是什么? Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性. 任何一个容器都可以指定为 F ...
- Java-Runoob-高级教程-实例-数组:10. Java 实例 – 查找数组中的重复元素-un
ylbtech-Java-Runoob-高级教程-实例-数组:10. Java 实例 – 查找数组中的重复元素 1.返回顶部 1. Java 实例 - 查找数组中的重复元素 Java 实例 以下实例 ...
- android 用户界面教程实例汇总
1.android用户界面之WebView教程实例汇总 http://www.apkbus.com/android-51718-1-1.html 2.android用户界面之Notification教 ...
- Java-Runoob-高级教程-实例-方法:11. Java 实例 – enum 和 switch 语句使用
ylbtech-Java-Runoob-高级教程-实例-方法:11. Java 实例 – enum 和 switch 语句使用 1.返回顶部 1. Java 实例 - enum 和 switch 语句 ...
- android用户界面之AlarmManager教程实例汇
qianqianlianmeng android用户界面之AlarmManager教程实例汇 AlarmManager使用教程 1. 如何利用AlarmManager机制在一定时间后启动某Activi ...
最新文章
- ELK6.0已取消filebeat配置document_type
- TensorFlow中文社区论坛 发布上线!
- 解决了无法显示验证码的问题
- 【图解机器学习】人人都能懂的算法原理
- sklearn自学指南(part52)--潜在狄利克雷分配(LDA)
- 格式化字符串漏洞利用 五、爆破
- 图解TCPIP-IP 网际协议-IP包
- maven spring hibernate shiro
- Linux 命令(37)—— free 命令
- Word Frequency(Leetcode192)
- 织梦列表页list标签调用支持flag属性方法
- 【Python从入门到精通】(一)就简单看看Python吧
- 熔断机制什么意思_什么是熔断机制 熔断机制是什么意思
- Java工程师薪资究竟有多高?
- 暗原色先验图像去雾算法研究_先验算法
- html怎么制作气泡,制作CSS气泡框
- java+mysql crm客户关系区块链毕业管理系统设计与论文
- mysql 计算中位数_【转】MySQL如何计算中位数
- Fresco图片加载框架的介绍,相关开源库以及工具类的封装
- 在 Microsoft Edge 浏览器上安装 Vue 项目调试扩展插件 Vue-Devtools
热门文章
- 机器学习中的数学——Adam(Adaptive Moments)
- stm32F103上基于FreeRTOS系统的亮度可调小台灯
- 时间序列之格兰杰因果关系检验(4)
- 零基础学习嵌入式开发难吗?嵌入式开发需要学习什么
- 对python的认识作文500字_关于启示的作文500字
- 动态数组的使用之char *res=new char(strlen(src)+1)
- 关注天气:免费的短信天气预报
- layout_constraintWidth_percent in java
- 【转载】裸眼识别二维码
- 第一章、Ansible的详细介绍与安装