- iss:jwt签发者
- sub:jwt所面向的用户
- aud:接收jwt的一方
- exp:jwt的过期时间,这个过期时间必须大于签发时间
- nbf:定义在什么时间之前,该jwt都是不可用的
- iat:jwt的签发时间
- jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
公共的声明:
公共的声明可以添加任何的信息,一般添加用户的相关信息或其它业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密;
私有的声明
私有的声明是提供者和消费者功能定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为名文信息。
定义一个payload
{ “sub”: “1234567890”, “name”: “John Doe”, “admin”: true }
然后将其base64加密,得到jwt的一部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
Signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header(base64后的)
- payload(base64后的)
- secred
这个部分需要base64加密后的header和base64加密后的payload使用“.”连接组成的字符串,然后通过header中声明的加密方式进行加secret组合加密,然后就构成了jwt的第三部分
var encodedString = base64UrlEncode(header) + ‘.’ + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, ‘secret’); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用“.”连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发也是在服务端的,secret就是用来进行jwt的签发和jwt的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret,那就意味着客户端可以自我签发jwt了
应用
一般是在请求头里加入Authorization,并加上Bearer标注:
fetch(‘api/user/1’, {
headers: { ‘Authorization’: 'Bearer ’ + token
}
})
JWT有什么不好的地方?
不好的地方就是JWT等于说是泼出去的水,发出去的令牌,不可更改,只要有效期内就可以永使用
JWT安全性如何?
这也是JWT最重要的一点,它的出现就是为了免登陆或者权限控制,所以它的安全性至关重要。所以它虽然是一直加密的,但是安全性总是建立在相对性之上的。所以只能说一定程度上是安全的。
JWT如何存放?
JWT不需要存放,或者说存放在客户端那里。服务端只需要有解密算法就可以判断当前的JWT是不是有效的。
JWT如何验证?
当接收方接收到一个JWT的时候,首先要对这个JWT的完整性进行验证,这个就是签名认证。它验证的方法其实很简单,只要把header使用base64url解码以后就知道签发者用什么算法做的签名,然后用这个算法对header和payload加密一下,再比较这个签名是否和JWT本身包含的signature是否完全相同,只要不同就可以认为这个JWT是被篡改过的。所以接收方生成签名的时候必须使用跟JWT发送方相同的密钥。
再次就是验证payload里的部分,比如是否过期,还有自定义的信息是否相同,加上已经前面验证的signature,这样就保证了这个签名的有效性
互斥登录/修改密码后登录的问题
场景:用户在多个设备或者其它浏览器/IP登录以后或者修改密码以后,那其它的设备就需要自动退出登录
- 用户修改密码之后签发一个新的token来覆盖之前的。但是之前的token只要在失效之前仍然是可以验证并且登录的
- 用户手动退出的时候也是需要token失效的,这就需要退出的时候直接清除或者覆盖客户端保存的token。同上,之前的token仍然有效,所以token的时间不能太长
- 设置黑名单,在签发新token的时候把之前未过期的token加入黑名单使之失效。但是这样就需要储存token,而jwt的优点就是不需要储存空间。
续签
场景:需要用户长期登录的应用,比如用你开发需要用户保持登录的应用,在使用token的时候保证用户体验不需要频繁登录操作,又要保证用户在一段时间之后登录过期重新登录,主要是一个用户体验和用户信息安全的问题。这个时候又要求token不能过长,只能通过续签来保持登录状态,又或者说用户登录的时候你需要获取用户在社交网站的信息,就需要获取社交网站授权的token,这也需要续签,不过这是向社交网站续签。
何时续签:
1.每次请求刷新 每次请求直接覆盖签发新的token
2.记录时间定时刷新 设置一个定时器,记录签发时间,然后到过期的时候签发新的
3.临期时刷新 访问的时候对比时间,如果即将过期就立即刷新。
4.过期后续签,这样设置过期时间的意义就在于如果token被盗取后黑客登录也需要续签,这样用户再次登录客户端就会发现用户登录异常。而且每次获取的token都是新的,旧的只能>获取一次续签的机会,也就说如果用户续签以后之前的token就不能再续签了。
5.jwt-autorefresh(JWT自动刷新)(有限制条件,在客户端定时刷新)。
JWT的优点
- 因为json的通用性,所以JWT是可以跨语言支持的,像C#,JavaScript,NodeJS,PHP等许多语言都可以使用
- 因为由了payload部分,所以JWT可以在自身存储一些其它业务逻辑所必要的非敏感信息
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
- 它不需要在服务端保存会话信息,所以它易于应用的扩展
JWT 安全相关
- 不应该在jwt的payload部分存储敏感信息,因为该部分是客户端可解密的部分
- 保护好secret私钥。该私钥非常重要
- 如果可以,请使用https协议
JWT生成token的安全性分析
从JWT生成的token组成上来看,要想避免token被伪造,主要就得看签名部分了,而签名部分又有三部分组成,其中头部和载荷的base64编码,几乎是透明的,毫无安全性可言,那么最终守护token安全的重担就落在了加入的盐上面了!试想:如果生成token所用的盐与解析token时加入的盐是一样的。岂不是类似于中国人民银行把人民币防伪技术公开了?大家可以用这个盐来解析token,就能用来伪造token。这时,我们就需要对盐采用非对称加密的方式进行加密,以达到生成token与校验token方所用的盐不一致的安全效果!
非对称加密RSA介绍
基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端私钥加密,持有私钥或公钥才可以解密公钥加密,持有私钥才可解密 优点:安全,难以破解 缺点:算法比较耗时,为了安全,可以接受 历史:三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA。
JWT流程图
5.1 创建工程
环境:idea、maven、jdk8、springboot2.x
maven结构图
<?xml version="1.0" encoding="UTF-8"?>pom依赖
<project xmlns=“http://maven.apache.org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”>
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.3
com.uncle
jwt-demo
0.0.1-SNAPSHOT
jwt-demo
Demo project for Spring Boot
<java.version>1.8</java.version>
com.auth0
java-jwt
3.4.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
fastjson
1.2.48
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
5.2 自定义注解
package com.uncle.jwtdemo.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
-
@program: jwt-demo
-
@description: 跳过验证
-
@author: 步尔斯特
-
@create: 2021-08-07 16:18
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
package com.uncle.jwtdemo.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
-
@program: jwt-demo
-
@description: 需要登录才可以进行操作
-
@author: 步尔斯特
-
@create: 2021-08-07 16:19
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
5.3 用户实体类
package com.uncle.jwtdemo.bean;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String Id;
String username;
String password;
//获取token的方法
public String getToken(User user) {
String token=“”;
token= JWT.create().withAudience(user.getId())
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
}
5.4 创建拦截器
package com.uncle.jwtdemo.config;
import com.uncle.jwtdemo.interceptor.AuthenticationInterceptor;
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;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:25
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns(“/**”);
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
5.5 认证的拦截器
package com.uncle.jwtdemo.interceptor;
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.uncle.jwtdemo.annotations.PassToken;
import com.uncle.jwtdemo.annotations.UserLoginToken;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:22
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader(“token”);// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException(“无token,请重新登录”);
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException(“401”);
}
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException(“用户不存在,请重新登录”);
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException(“401”);
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
5.6 用户业务
package com.uncle.jwtdemo.service;
import com.uncle.jwtdemo.bean.User;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:30
*/
public interface UserService {
User findByUsername(User user);
User findUserById(String id);
}
package com.uncle.jwtdemo.service.impl;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.UserService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:48
*/
@Service
public class UserServiceImpl implements UserService {
private static Map<String, User> userMap = new HashMap<>();
@Override
public User findByUsername(User user) {
return userMap.get(user.getUsername());
}
@Override
public User findUserById(String id) {
return userMap.get(id);
}
static {
userMap.put(“1”, new User(“1”, “zhangsan”, “123”));
userMap.put(“zhangsan”,new User(“1”,“zhangsan”,“123”));
}
}
5.7 token业务
package com.uncle.jwtdemo.service;
import com.uncle.jwtdemo.bean.User;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:37
*/
public interface TokenService {
String getToken(User userForBase);
}
package com.uncle.jwtdemo.service.impl;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.TokenService;
import org.springframework.stereotype.Service;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 17:26
*/
@Service
public class TokenServiceImpl implements TokenService {
@Override
public String getToken(User userForBase) {
return userForBase.getToken(userForBase);
}
}
5.8 测试接口
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

学习分享,共勉
这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!
资料整理不易,读者朋友可以转发分享下!
Java核心知识体系笔记.pdf
中高级Java开发面试高频考点题笔记300道.pdf
架构进阶面试专题及架构学习笔记脑图
Java架构进阶学习视频分享
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-QNLiMFTW-1713471300690)]
[外链图片转存中…(img-TLLd9ERB-1713471300690)]
[外链图片转存中…(img-1xnCtoyU-1713471300691)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

学习分享,共勉
这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!
资料整理不易,读者朋友可以转发分享下!
Java核心知识体系笔记.pdf
[外链图片转存中…(img-q6AP0JAZ-1713471300691)]
中高级Java开发面试高频考点题笔记300道.pdf
[外链图片转存中…(img-7RGJdGH5-1713471300691)]
架构进阶面试专题及架构学习笔记脑图
[外链图片转存中…(img-h0Zbo5a4-1713471300691)]
Java架构进阶学习视频分享
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!