JWT是什么?如何使用?

JWT是什么?如何使用?

  • 前言
  • 什么是JWT?
    • 概念
    • 工作方式
    • JWT的组成
      • Header
      • Payload
      • Signatrue
    • 实战
      • 引入依赖
      • 自定义注解
      • 定义实体类
      • 定义一个JWT工具类
      • 业务校验并生成token
      • 定义拦截器
      • 配置拦截器
      • 定义接口方法并添加注解
      • 开始验证
    • 使用场景
    • 注意事项
  • JWT与传统session认证的区别

前言

搜索这篇文章的小伙伴一定对【基于传统的session认证方式】有所了解,这里就不过多介绍了,我们直奔主题。

基于传统的session认证方式有几个显著的缺点:

  • 每一个登录的用户都需要在服务端存储一个session信息,一般都存储在服务器内存中,当用户数量过多,会过度占用服务器内存,也会增加服务器压力。
  • 对于分布式系统,还要需要解决共享session的问题。

那么,谁能够解决传统session认证方式存在的问题呢,他就是JWT。

什么是JWT?

概念

JWT(JSON WEB TOKEN):它是一种紧凑、安全的表示双方之间传输声明的方法。JWT是一个包含头部(Header)、负载(Payload)和签名(Signature)的JSON对象。JWT可用于认证和授权用户,它们是自包含的,意味着验证它们所需的所有信息都包含在令牌本身中。

  • 紧凑型:数据体积小,可通过POST请求参数或HTTP请求头发送。

  • 自包含:JWT 包含了主体的所有信息,避免了每个请求都需要向 Uaa 服务验证身份,降低了服务器的负载。

工作方式

  • 客户端登录时将用户信息传递给服务器,服务器使用用户信息通过密钥创建JWT,服务器不需要进行存储,直接将JWT返回给浏览器。
  • 客户端浏览器拿到JWT 后,存储在浏览器中,对于以后的每次请求,都不需要再通过授权服务来判断该请求的用户 以及该用户的权限。在微服务系统中,可以利用 JWT 实现单点登录

认证流程图如下:

在这里插入图片描述

JWT的组成

我们先来看看实际的JWT长什么样子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

很复杂,看不懂是不是?其实这一串是经过加密之后的密文字符串,中间通过.来分割。每个.之前的字符串分别表示JWT的三个组成部分:头部(Header)、负载(Payload)和签名(Signature)。

Header

Header的主要作用是用来标识。通常是两部分组成:

  • typ:type 的简写,令牌类型,也就是JWT。
  • alg:Algorithm 的简写,加密签名算法。一般使用HS256,jwt官网提供了12种的加密算法,截图如下:

在这里插入图片描述
Header的明文示例:

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

经过Base64编码之后的明文,变为:

eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9

也就是第一个.之前的密文串。以下是Header部分常用部分的声明:
在这里插入图片描述

Payload

也称为JWT claims,放置需要传输的信息,有三类:

  • 保留claims:主要包括iss发行者、exp过期时间、sub主题、aud用户等。
  • 公共claims:定义新创的信息,比如用户信息和其他重要信息。
  • 私有claims:用于发布者和消费者都同意以私有的方式使用的信息。

以下是Payload的官方定义内容:
在这里插入图片描述
Payload明文示例:

{"sub": "12344321","name": "Mars酱", // 私有claims"iat": 1516239022
}

经过Base64加密之后的明文,变为:

eyJzdWIiOiIxMjM0NDMyMSIsIm5hbWUiOiJNYXJz6YWxIiwiaWF0IjoxNTE2MjM5MDIyfQ

也就是第一个.和第二个. 之间的密文串内容。

Signatrue

Signature 部分是对Header和Payload两部分的签名,作用是防止 JWT 被篡改。这个部分的生成规则主要是是公式(伪代码)是:

Header中定义的签名算法(base64编码(header) + "." + base64编码(payload), secret
)

secret是存放在服务端加密使用到的盐。

得到签名之后,把Header的密文、Payload的密文、Signatrue的密文按顺序拼接成为一个字符串,中间通过.来连接并分割,整个串就是JWT了。

实战

这里使用SpringBoot框架。

引入依赖

引入JWT依赖,由于是基于Java,所以需要的是java-jwt。

<dependency>      <groupId>com.auth0</groupId>      <artifactId>java-jwt</artifactId>      <version>3.5.0</version>
</dependency>

自定义注解

在这一步,我们在annotation包下定义一个用户需要登录才能进行其他接口访问等一系列操作的注解TokenRequired。

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenRequired {    boolean required() default true;
}

@Target旨意为我们自定义注解@TokenRequired的作用目标,因为我们本次注解的作用目标为方法层级,因此使用 ElementType.METHOD。

@Retention旨意为我们自定义注解 @TokenRequired的保留位置,@TokenRequired的保留位置被定义为RetentionPolicy.RUNTIME这种类型的注解将被JVM保留,他能在运行时被JVM或其他使用反射机制的代码所读取和使用。

定义实体类

在entity包中,我们使用lombok,简单自定义一个实体类User。

@Data
@AllArgsConstructor
@NoArgsConstructorpublic 
class User {String Id;String username;String password;
}

定义一个JWT工具类

在这一步,我们在util包下面创建一个JwtUtil工具类,用于生成token和校验token。

public class JwtUtil {//过期时间15分钟    private static final long EXPIRE_TIME = 15*60*1000;         //生成签名,15分钟后过期    public static String sign(String username,String userId,String password){        //过期时间        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);        //使用用户密码作为私钥进行加密        Algorithm algorithm = Algorithm.HMAC256(password);        //设置头信息        HashMap<String, Object> header = new HashMap<>(2);        header.put("typ", "JWT");        header.put("alg", "HS256");        //附带username和userID生成签名        return JWT.create().withHeader(header).withClaim("userId",userId)                .withClaim("username",username).withExpiresAt(date).sign(algorithm);    }//校验token    public static boolean verity(String token,String password){        try {            Algorithm algorithm = Algorithm.HMAC256(password);            JWTVerifier verifier = JWT.require(algorithm).build();            verifier.verify(token);            return true;        } catch (IllegalArgumentException e) {            return false;        } catch (JWTVerificationException e) {            return false;        }    }
}

业务校验并生成token

在service包下,我们创建一个UserService,并定义一个login方法,用于做登录接口的业务层数据校验,并调取JwtUtil中方法生成token。

@Service("UserService")
public class UserService {    
@Autowired	UserMapper userMapper;    public String login(String name, String password) {String token = null;try {//校验用户是否存在            User user = userMapper.findByUsername(name);if (user == null) {ResultDTO.failure(new ResultError(UserError.EMP_IS_NULL_EXIT));} else {//检验用户密码是否正确                if (!user.getPassword().equals(password)) {ResultDTO.failure(new ResultError(UserError.PASSWORD_OR_NAME_IS_ERROR));} else {// 生成token,将 user id 、userName保存到 token 里面                    token = JwtUtil.sign(user.getUsername(), user.getId(), user.getPassword());}}} catch (Exception e) {e.printStackTrace();}return token;}
}

Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。

withAudience()存入需要保存在token的信息,这里我把用户ID存入token中。

定义拦截器

接下来我们需要写一个拦截器去获取token并验证token。

public class AuthenticationInterceptor implements HandlerInterceptor {@AutowiredUserService userService;@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {// 从 http 请求头中取出 tokenString token = httpServletRequest.getHeader("token");// 如果不是映射到方法直接通过if (!(object instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) object;Method method = handlerMethod.getMethod();//检查有没有需要用户权限的注解if (method.isAnnotationPresent(TokenRequired.class)) {TokenRequired userLoginToken = method.getAnnotation(TokenRequired.class);if (userLoginToken.required()) {// 执行认证if (token == null) {throw new RuntimeException("无token,请重新登录");}// 获取 token 中的 user idString userId;try {userId = JWT.decode(token).getClaim("userId").asString();} catch (JWTDecodeException j) {throw new RuntimeException("401");}User user = userService.findUserById(userId);if (user == null) {throw new RuntimeException("用户不存在,请重新登录");}// 验证 tokentry {if (!JwtUtil.verity(token, user.getPassword())) {throw new RuntimeException("无效的令牌");}} catch (JWTVerificationException e) {throw new RuntimeException("401");}return true;}}return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {}
}

AuthenticationInterceptor拦截器实现了HandlerInterceptor接口的三个方法:

  • boolean preHandle ():
    预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器,自定义Controller返回值,返回值为true会调用下一个拦截器或处理器,或者接着执行postHandle()和afterCompletion();false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。

  • void postHandle():
    后处理回调方法,实现处理器的后处理(DispatcherServlet进行视图返回渲染之前进行调用),此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null。

  • void afterCompletion():
    整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行,也就是在DispatcherServlet渲染了对应的视图之后执行。用于进行资源清理。

该拦截器的执行流程为:

  1. 从 http 请求头中取出 token;
  2. 检查有没有需要用户权限的注解,如果需要,检验token是否为空;
  3. 如果token不为空,查询用户信息并校验token;
  4. 校验通过,则进行业务访问处理,校验失败则返回token失效信息。

配置拦截器

在配置类上添加了注解@Configuration,标明了该类是一个配置类并且会将该类作为一个SpringBean添加到IOC容器内。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Beanpublic AuthenticationInterceptor authenticationInterceptor() {return new AuthenticationInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 将我们上步定义的实现了HandlerInterceptor接口的拦截器实例authenticationInterceptor添加InterceptorRegistration中,并设置过滤规则,所有请求都要经过authenticationInterceptor拦截。registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");}
}

WebMvcConfigurer接口是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件来实现基本的配置需要。

InterceptorConfig内的addInterceptor需要一个实现HandlerInterceptor接口的拦截器实例,addPathPatterns方法用于设置拦截器的过滤路径规则。

在addInterceptors方法中,我们将第6步定义的实现了HandlerInterceptor接口的拦截器实例authenticationInterceptor,添加至InterceptorRegistration中,并设置过滤路径。现在,我们所有请求都要经过authenticationInterceptor的拦截,拦截器authenticationInterceptor通过preHandle方法的业务过滤,判断是否有@TokenRequired 来决定是否需要登录。

定义接口方法并添加注解

@RestController
@RequestMapping("user")
public class UserController {@AutowiredUserService userService;/*** 用户登录     * @param user     * @return*/@PostMapping("/login")public ResultDTO login(User user) {String token = userService.login(user.getUsername(), user.getPassword());if (token == null) {return ResultDTO.failure(new ResultError(UserError.PASSWORD_OR_NAME_IS_ERROR));}Map<String, String> tokenMap = new HashMap<>();tokenMap.put("token", token);return ResultDTO.success(tokenMap);}@TokenRequired@GetMapping("/hello")public String getMessage() {return "你好哇,我是小码仔";}
}

不加注解的话默认不验证,登录接口一般是不验证的。所以我在getMessage()中加上了登录注解,说明该接口必须登录获取token后,在请求头中加上token并通过验证才可以访问。

开始验证

我在代码中对getMessage()添加了@TokenRequired注解,此刻访问该方法时必须要通过登录拿取到token值,并在请求头中添加token才可以访问。我们现在做以下校验:

  1. 直接访问,不在请求头里添加token:
    在这里插入图片描述
    如上图所示,请求结果显示:无token,请重新登录。

  2. 访问登录接口,获取token,并在请求头中添加token信息:
    在这里插入图片描述
    此时,访问成功。

  3. 15分钟后,token失效,我们再次在请求头中添加token信息访问:
    在这里插入图片描述
    此时token已失效,返回:无效的令牌。

使用场景

  • 授权:这是使用 JWT 的最常见的使用场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用,还能解决CSRF问题。
  • 信息交换:JWT是在各方之间安全传输信息的比较便捷的方式。由于 JWT 可以签名(例如,使用公钥/私钥对),因此可以确定发送者是否是在您的授权范围之内。并且,由于签名是使用标头和有效负载计算的,因此还可以验证内容是否未被篡改。

注意事项

  • 不要存储敏感信息:JWT的Payload是可解码的,不应存储敏感信息,一般存储用户id。
  • 保护密钥:确保用于签名JWT的密钥安全且不可预测。
  • 设置合理的过期时间:避免JWT有效期过长。
  • HTTPS:在通过网络发送JWT时应使用HTTPS,以保护其免受中间人攻击。

JWT与传统session认证的区别

  • 工作原理不同:
    • Session 机制依赖于服务器端的存储。当用户首次登录时,服务器会创建一个会话,并生成一个唯一的会话 ID,然后将这个 ID 返回给客户端(通常是通过Cookie)。客户端在后续的请求中会携带这个会话 ID,服务器根据会话ID来识别用户并获取其会话信息;
    • JWT 是一种无状态的认证机制,它通过在客户端存储令牌(Token)来实现认证。当用户登录时,服务器会生成一个包含用户信息和有效期的 JWT,并将其返回给客户端。客户端在后续的请求中会携带这个 JWT,服务器通过验证 JWT 的有效性来识别用户。
  • 存储方式不同:
    • Session 信息存储在服务器端,通常是保存在内存或数据库中。这种方式需要服务器维护会话状态,因此在分布式系统或微服务架构中,会话信息的共享和同步可能会成为问题;
    • JWT信息存储在客户端,通常是保存在浏览器的本地存储或 HTTP 请求的头部中。这种方式无需服务器维护会话状态,使得 JWT 在分布式系统或微服务架构中更加灵活和易于扩展。
  • 有效期和灵活性不同:
    • Session 的有效期通常由服务器控制,并且在会话期间用户状态可以在服务器端动态改变。但这也意味着服务器需要管理会话的生命周期;
    • JWT 的有效期可以在令牌生成时设置,并且可以在客户端进行缓存和重复使用。这使得 JWT 在需要频繁访问资源且不需要频繁更改用户状态的场景中更加适用。此外,JWT 还支持在令牌中包含自定义的用户信息,提供了更大的灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/830632.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用大模型生成带文字的海报

本文代码讲整合在&#xff1a; GitHub - liangwq/Chatglm_lora_multi-gpu: chatglm多gpu用deepspeed和 这篇文章介绍如何利用VLMdiffusion模型来搭建一条文本生成海报的链路。搭建这条链路有两个应用&#xff1a;1.实际的业务中需要批量生产文字图海报可以用&#xff0c;2.可以…

生成式人工智能(AIGC)教学解决方案

一、前言 近年来&#xff0c;伴随计算能力跃升和数据量指数级增长&#xff0c;以多模态巨型模型为典型代表的生成式AI技术&#xff0c;在全球范围内引起了广泛关注与热烈追捧。在教育、医疗、法律等众多专业领域&#xff0c;生成式人工智能技术的影响力日益凸显&#xff0c;尤…

C++11 设计模式5. 原型模式

什么是原型模式&#xff1f; 原型模式⼀种创建型设计模式&#xff0c;该模式的核⼼思想是基于现有的对象创建新的对象&#xff0c;⽽不是从头开始创建。在原型模式中&#xff0c;通常有⼀个原型对象&#xff0c;它被⽤作创建新对象的模板。新对象通过复制原型对象的属性和状态来…

24.什么是跨域?解决方案有哪些?

为什么会出现跨域问题 存在浏览器同源策略&#xff0c;所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到&#xff0c;跨域限制主要的目的就是为了用户的上网安全。 同源策略导致的跨域是浏览器单方面拒绝响应数据&#xff0c;服务器端是处理完毕…

opencv基础篇 ——(十)非真实感渲染

非真实感渲染&#xff08;Non-Photorealistic Rendering, NPR&#xff09;是指通过一系列图像处理技术&#xff0c;将真实感图像转换为具有特定艺术风格或视觉效果的图像&#xff0c;模拟绘画、素描、卡通等非现实主义表现手法。OpenCV 提供了一些内置函数来实现非真实感渲染&a…

2024最新的,免费的 ChatGPT 网站AI(八个)

ChatGPT是美国人工智能研究实验室OpenAI在2022年11月推出的一款人工智能技术驱动的语言模型应用。它基于GPT-3.5架构&#xff08;后续还有GPT-4架构的升级版&#xff09;构建&#xff0c;拥有强大的自然语言处理能力和上下文理解能力&#xff0c;能够参与多轮对话&#xff0c;为…

Python_GUI工具包 PyQt 与 Pyside6的介绍

Python_GUI工具包 PyQt 与 Pyside6的介绍 一、简介 在Python的GUI&#xff08;图形用户界面&#xff09;开发领域&#xff0c;PyQt和PySide6是两个非常重要的工具包。它们都基于Qt库&#xff0c;为Python开发者提供了丰富的GUI组件和强大的功能。当然Python也有一些其他的GU…

Halcon 检测物体定位点

文章目录 get_domain 返回所有输入图像的定义域作为一个区域add_channels 给区域增加灰度值find_shape_model 发现匹配模板find_shape_models 发现最佳模板示例 get_domain 返回所有输入图像的定义域作为一个区域 Halcon 中的区域 get_domain(Image : Domain : : ) Image : …

SpringBoot之自定义注解参数校验

SpringBoot之自定义注解参数校验 为什么要自定义注解 我这里先引入一个例子&#xff0c;就比如我现在要写文章&#xff0c;文章也许写完正要发布&#xff0c;也可以是还没写完正要存草稿&#xff0c;前端往后端发送数据&#xff0c;如果前端的state不是草稿或者已发布状态&…

大模型应用开发极简入门

简单的归纳一下书的前序部分 目录 LLM&#xff08;Large Language Model&#xff09;的应用技术栈通常包括以下几个方面&#xff1a; 深度学习框架&#xff1a; 数据预处理工具&#xff1a; 训练资源&#xff1a; 模型优化和调参工具&#xff1a; 部署和应用集成&#xf…

智慧图书管理|基于SSM+vue的网上服装商城系统(源码+数据库+文档)

智慧图书管理目录 基于SSMvue的网上服装商城系统 一、前言 二、系统设计 三、系统功能设计 1.1 服装列表 1.2 公告信息管理 1.3 公告类型管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1…

linus下Anaconda创建虚拟环境pytorch

一、虚拟环境 1.创建 输入下面命令 conda create -n env_name python3.8 输入y 2.激活环境 输入 conda activate env_name 二、一些常用的命令 在Linux的控制平台 切换到当前的文件夹 cd /根目录/次目录 查看conda目录 conda list 查看pip目录 pip list查看历史命…

Python 爬虫如何配置代理 IP (Py 采集)

在Python中配置代理IP&#xff0c;可以通过设置requests库的proxies参数来实现。以下是一个示例&#xff1a; import requests# 则立可以获取稳定代理Ip&#xff1a;https://www.kuaidaili.com/?refrg3jlsko0ymg # 推荐使用私密动态 IP proxies {"http": "ht…

芒果超媒财报解读:科技加持下,如何蜕变为内容“全科生”?

在降本增效和内容为王的基调下&#xff0c;国内头部长视频平台正在拥抱增长。 爱奇艺率先公布2023年财务数据&#xff0c;实现归母净利润19.25亿元&#xff0c;与2022年亏损1.36亿元相比&#xff0c;扭亏为盈且增幅显著。 而近日&#xff0c;随着新一季《浪姐》播出&#xff…

JAVA面试题分享---多线程与线程池

多线程 什么是线程?线程和进程的区别?&#xff08;了解&#xff09; 线程&#xff1a;是进程的一个实体&#xff0c;是 cpu 调度和分派的基本单位&#xff0c;是比进程更小的 可以独立运行的基本单位。 进程&#xff1a;具有一定独立功能的程序关于某个数据集合上的一次运…

【漏洞复现】IP-guard WebServer 权限绕过漏洞

0x01 产品简介 IP-guard WebServer 是 IP-guard 网络安全管理系统的一部分,用于提供 Web 界面以进行用户权限管理、监控和审计。 0x02 漏洞概述 IP-guard WebServer的权限验证机制中存在设计缺陷,未授权的攻击者能够规避安全验证,通过后端接口执行文件的任意读取和删除操…

DaVinci Resolve Studio 19(达芬奇19调色剪辑)win/mac激活版

DaVinci Resolve Studio是一个结合专业的8k 编辑&#xff0c;颜色混合&#xff0c;视觉效果和音频后期制作的软件。只需点击一下&#xff0c;你就可以立即在编辑、混音、特效和音频流之间切换。此外&#xff0c;达芬奇解决(达芬奇)是一个多用户协作的解决方案&#xff0c;使编辑…

一单利润100+,不起眼的小生意,却能闷声发财!

今天&#xff0c;我想向大家介绍一个看似不太热门&#xff0c;但实际上需求很高的项目——酒店代订。这个项目其实很早以前就已经有人开始尝试了&#xff0c;但可能并没有被大众所熟知。简而言之&#xff0c;酒店代订就是帮助他人通过我们来预订他们想要入住的酒店。 当客户将…

管理能力学习笔记八:Will-Skill矩阵“盘“团队

如何把握带教中的“度”&#xff0c;才能在把事情做好的基础上&#xff0c;又能使员工获得成长呢&#xff1f; 需要做到 合理授权 & 适当辅导 如何做到&#xff1f; 通过使用 意愿-技能矩阵(Will-Skill Matrix) 辨别不同带教方法的适用情形&#xff0c;"盘"…

ElasticSearch总结1

目录 一、ElasticSearch介绍&#xff1a; 举例一&#xff1a; 举例二&#xff1a; 举例三&#xff1a; 二、ELK技术栈 三、Elasticsearch 的基本概念&#xff1a; 四、正向索引和倒排索引&#xff1a; 正向索引&#xff1a; 倒排索引&#xff1a; 五、Mysql和Elastics…