365bet世界杯

JWT技术分享(1)

发布时间 2025-09-20 15:56:53 作者 admin 阅读 2102

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!

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的时候,首先要对这个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流程图

五、JWT认证的代码实现

5.1 创建工程

环境:idea、maven、jdk8、springboot2.x

maven结构图

pom依赖

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

1.8

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 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 测试接口

总结

其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。

这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来

目录:

部分内容截图:

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取! 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 测试接口

总结

其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。

这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来

目录:

[外链图片转存中…(img-FNimPxeo-1714699910359)]

部分内容截图:

[外链图片转存中…(img-NbXGvuXx-1714699910360)]

[外链图片转存中…(img-9mgYipHr-1714699910360)] 《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!

相关推荐

手机chm阅读器

chm阅读器免费版 2024-08-08 / 24 MB 下载 推荐理由:chm阅读器免费版是一款操作简便的微软文档多功能阅读器,支持chm格式电子书的打开和浏览,在这

07-07 分类 365bet世界杯

简单易上手的洛阳白菜

简单易上手的洛阳白菜 6087人浏览 104人收藏 0人做过 APP中查看更多做法 作者: 泽斯夕月 还是老早的时候在饭店吃到的一款菜,饭店起的名叫洛

07-22 分类 365bet世界杯