Jwt 如何在 springboot 项目中进行接口访问鉴权

文章目录

    • 1 springboot 框架负责接口的拦截和放行
      • 1.1 原理
      • 1.2 思路
      • 1.3 坑: Springboot 访问了错误处理路径 `/error`
    • 2 jwt token 负责携带数据和签名的生成及校验
      • 2.1 初始化
      • 2.2 设置 Header
      • 2.3 携带数据 payload
      • 2.4 签名 sign 后, 生成 token
      • 2.5 校验
      • 2.6 获取信息
      • 2.7 字段说明
    • 3 拦截器代码
    • 其它

结合以下文章:
jwt.io 官网详细介绍

SpringBoot集成JWT实现Token登录验证

SpringBoot项目使用JWT+拦截器实现token验证

spring-boot + JWT实现TOKEN登录接口验证

SpringBoot集成JWT实现token验证

SpringBoot 开发 – JWT 认证教程

1 springboot 框架负责接口的拦截和放行

1.1 原理

使用 HandlerInterceptor (对于 springboot 框架不推荐使用 doFilter)

1.2 思路

白名单思路, 拦截所有接口目录/**, 放行需要的接口

@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
}

1.3 坑: Springboot 访问了错误处理路径 /error

接口程序中有未处理的异常, 报了 Null Pointer Exception, Springboot 调用默认的错误处理接口 /error 企图调用错误处理程序, 第二次触发了 HandlerInterceptor, 由于/error不带 token, 所以被拒绝,最终报 token 校验不通过的错误信息.

这里的解决方法:

1 处理程序中所有异常, 在最外层捕捉不可预见的异常, 返回统一错误信息,服务内部错误
2 自定义 springboot 的 error path 为符合自己程序的路径, 并用 controller 定义处理程序. 当springbot框架本身或者依赖包出现不可预知的错误时,转到这里, 可以返回统一错误信息

其它方法也可以尝试使用 @ControllerAdvice 自定义异常处理类, 处理程序自身不可预知的错误

2 jwt token 负责携带数据和签名的生成及校验

官方库

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

2.1 初始化

JWTCreator.Builder builder = JWT.create();

2.2 设置 Header

Map<String,Object> headerMap = new HashMap<>();
builder.withHeader(headerMap);

2.3 携带数据 payload

自定义数据

for (Map.Entry<String,String> entry:data.entrySet()) {builder.withClaim(entry.getKey(), entry.getValue());
}

设置过期时间

builder.withExpiresAt(expireDate);

Token 放在请求Header的Authorization字段里。Token 携带数据userId

Token 的格式:

header

{"kid": "XXXXXXXXXXXXXXXXXX0MDVmLWIyMjEtMjQ1MWU3NWYxXXXXX5","typ": "JWT","alg": "RS256"
}

payload

{"exp": 1684829637,"userId": "xxxxxxxxxxxx==","iat": 1684829607
}

2.4 签名 sign 后, 生成 token

token = builder.sign(Algorithm.HMAC256(secretKey))

如果使用RSA非对称算法,可以从jwt库的源码看出使用私钥签名

token = builder.sign(Algorithm.RSA256(rsaPrivateKey))

java-jwt-4.0.0-sources.jar!/com/auth0/jwt/algorithm/RSAAlgorithm.java

    @Overridepublic byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException {try {RSAPrivateKey privateKey = keyProvider.getPrivateKey();if (privateKey == null) {throw new IllegalStateException("The given Private Key is null.");}return crypto.createSignatureFor(getDescription(), privateKey, headerBytes, payloadBytes);} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {throw new SignatureGenerationException(this, e);}}

2.5 校验

配置算法

JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secretKey)).build();

如果使用RSA非对称算法,可以从jwt库的源码看出使用公钥校验

JWTVerifier verifier = JWT.require(Algorithm.RSA256(rsaPublicKey)).build();

java-jwt-4.0.0-sources.jar!/com/auth0/jwt/algorithm/RSAAlgorithm.java

    @Overridepublic void verify(DecodedJWT jwt) throws SignatureVerificationException {try {byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature());RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());if (publicKey == null) {throw new IllegalStateException("The given Public Key is null.");}boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes);if (!valid) {throw new SignatureVerificationException(this);}} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException| IllegalArgumentException | IllegalStateException e) {throw new SignatureVerificationException(this, e);}}

校验

DecodedJWT decodedJWT  = verifier.verify(token);

校验的方法是再生成一遍进行比较

2.6 获取信息

两种方法

  • 第一种方法: 在 HandlerInterceptor 里的 PreHandle 校验通过后, 立即解析 token, 拿到数据. 把解析结果放入 threadlocal 变量, 这样在整个请求的主线程里, 可以使用该变量, 并且该变量对其它线程不可见, 在请求结束的 afterCompletion() 方法里把 threadlocal 变量注销释放.
  • 第二种方法: 在需要获取信息的时候, 先获取该severlet请求的上下文 RequestContextHolder, 进而拿到请求Request中的 header, 进而拿到 token, 重新解析 token, 获取数据. 由于接口进来时, 已经通过校验, 可以不通过校验的方式获取解析后的token, 直接调用解析方法进行解析即可.

2.7 字段说明

nbf 可用于多机部署时, 服务器之间时间的微小差异

3 拦截器代码

Springboot 建议使用HandlerInterceptor进行拦截

定义 annotation, 对这个annotation 修饰的接口进行拦截


@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessWithoutToken {boolean required() default true;
}
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {log.info("Request from {} to URI: {}, URL: {}", HttpClientUtil.getRemoteIp(request), request.getRequestURI(), request.getRequestURL().toString());// 如果不是映射到方法直接通过if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();log.info("Method {}, {}", method.getName(), method.getDeclaredAnnotations());if (method.isAnnotationPresent(AccessWithoutToken.class)) {AccessWithoutToken accessWithoutToken = method.getAnnotation(AccessWithoutToken.class);if (accessWithoutToken.required()) {return true;}}// Authorization: Bearer <token>String authorization = request.getHeader("Authorization");// TODO check tokenresponse.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");if (!org.apache.commons.lang3.StringUtils.isBlank(authorization)) {String[] authorizationStr= StringUtils.split(authorization, SPACE);if (2 == authorizationStr.length) {String authType = authorizationStr[0];String token = authorizationStr[1];if (authType.equals("Bearer") && !org.apache.commons.lang3.StringUtils.isBlank(token)) {DecodedJWT decodedJWT = JwtUtil.verifyToken(token);if (null != decodedJWT) {// TODO 校验通过获取信息log.info("token: {}......, 校验通过, 签发时间{}, userId{}", token.substring(0, 32), decodedJWT.getIssuedAt().getTime(), decodedJWT.getClaim("userId"));return true;}} else {log.error("Token 校验失败, auth prefix={}, token={}", authType, token);}} else {log.error("Token 校验失败, http header 中解析 Authorization 字段错误, authorization={}", authorization);}} else {log.error("Token 校验失败, http header 中没有 Authorization 字段, authorization={}", authorization);}try (PrintWriter writer = response.getWriter()) {writer.print(RestResponse.fail(RestCode.USER_VALIDATE_FAIL_JWT_TOKEN));} catch (Exception e) {log.error("登录 JWT Token 校验失败 未知错误 error=", e);}return false;}
}

扩展阅读

OWASP Top Ten 2021 : Related Cheat Sheets

okta What-is-the-lifetime-of-the-JWT-tokens

其它

关于springboot 默认 error path

customize springboot default error path
ErrorMvcAutoConfiguration.java
get-started-with-custom-error-handling-in-spring-boot-java/
spring-boot-custom-error-page
how-to-fix-spring-boot-customize-http-error-response-in-java
howto.actuator.customize-whitelabel-error-page
boot-features-error-handling

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

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

相关文章

WebGL在教育和培训的应用

WebGL在教育和培训领域具有广泛的应用&#xff0c;其强大的图形渲染能力和跨平台性使得它成为创建交互式、视觉化的数字内容的理想选择。以下是一些WebGL在教育和培训上的应用示例&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司…

PlatEMO UI 界面

&#x1f389; 博主相信&#xff1a; 有足够的积累&#xff0c;并且一直在路上&#xff0c;就有无限的可能&#xff01;&#xff01;&#xff01; &#x1f468;‍&#x1f393;个人主页&#xff1a; 青年有志的博客 &#x1f4af; Github 源码下载&#xff1a;https://github.…

xpath 解析(基础)

解析xml 首先要下载包&#xff1a;pip install lxml 基本使用如下代码所示&#xff1a; # xpath 解析&#xff1a;先安装lxml:pip install lxml from lxml import etreexml """ <book><id>1</id><name>山花遍地开</name><pr…

nmap端口扫描工具安装和使用方法

nmap&#xff08;Network Mapper&#xff09;是一款开源免费的针对大型网络的端口扫描工具&#xff0c;nmap可以检测目标主机是否在线、主机端口开放情况、检测主机运行的服务类型及版本信息、检测操作系统与设备类型等信息。本文主要介绍nmap工具安装和基本使用方法。 nmap主…

【Java】编写一个简单的Servlet程序

Java Servlet 是运行在 Web 服务器或应用服务器上的程序&#xff0c;它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 使用 Servlet&#xff0c;可以收集来自网页表单的用户输入&#xff0c;呈现来自数据库或者其他源的记录…

LC24. 两两交换链表中的节点

代码随想录 class Solution {// 举例子:假设两个节点 1 -> 2// 那么 head 1; next 2; next.next null// 那么swapPairs(next.next),传入的是null,再下一次递归中直接返回null// 因此 newNode null// 所以 next.next head; > 2.next 1; 2 -> 1// head.next…

在MongoDB中使用数组字段和子文档字段进行索引

本文主要介绍在MongoDB使用数组字段和子文档字段进行索引。 目录 MongoDB的高级索引一、索引数组字段二、索引子文档字段 MongoDB的高级索引 MongoDB是一个面向文档的NoSQL数据库&#xff0c;它提供了丰富的索引功能来加快查询性能。除了常规的单字段索引之外&#xff0c;Mong…

Ubuntu 常用命令之 shutdown 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 shutdown 是 Ubuntu 系统下的一个命令&#xff0c;用于关闭或重启系统。这个命令可以让系统在一个特定的时间点进行关机或者重启&#xff0c;也可以立即执行。 shutdown 命令的基本格式如下 shutdown [选项] 时间 [警告消息]选项…

react当中生命周期(旧生命周期详解)

新生命周期https://blog.csdn.net/kkkys_kkk/article/details/135156102?spm1001.2014.3001.5501 目录 什么是生命周期 react中的生命周期 旧生命周期 生命周期图示 常用的生命周期钩子函数 初始化阶段 挂载阶段 在严格模式下挂载阶段的生命周期函数会执行两次原因 更…

软件渗透测试有哪些测试流程?权威安全测试报告的重要性

软件渗透测试也是安全测试的一种&#xff0c;是通过模拟恶意黑客的攻击方法&#xff0c;来评估计算机网络系统安全的一种评估方法。作为网络安全防范的一种新技术&#xff0c;对于网络安全组织具有实际应用价值。 一、软件渗透测试的过程   软件渗透测试的过程通常包括四个主…

前端学习——vuex的入门

学习一门技术最快捷的方式就是先了解其概念和使用场景&#xff0c;毕竟任何技术的出现都是为了解决某一个场景下的通用解决方案&#xff0c;并且使用最合理的方式去解决问题。 那么什么是vuex&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中…

JavaScript高级 ES6篇

前面的内容主要讲的是JavaScript基础内容&#xff0c;想要更加了解前端的小朋友们&#xff0c;需要再继续努力&#xff0c;我们开始向JavaScript高级内容出发了呦&#xff0c;不要掉队啦&#xff01; 一、变量与常量的声明 1.1 var 关键字声明变量 语法&#xff1a; var 变…

基于ssm+jsp学生综合测评管理系统源码和论文

网络的广泛应用给生活带来了十分的便利。所以把学生综合测评管理与现在网络相结合&#xff0c;利用java技术建设学生综合测评管理系统&#xff0c;实现学生综合测评的信息化。则对于进一步提高学生综合测评管理发展&#xff0c;丰富学生综合测评管理经验能起到不少的促进作用。…

轻量级Python IDE使用(二)——缩进规则

1、缩进对程序的影响 1.1、缩进不符合规则&#xff0c;解析器会报错&#xff0c;程序无法运行 a 0print(a)print(a)的缩进不符合缩进规则&#xff0c;程序在第二行发生错误。 1.2、符合规则&#xff0c;但缩进位置不同&#xff0c;程序执行的逻辑会有差异。 isP False if…

OPC UA 与PROFINET比较

ROFINET和OPC UA是两种常见的协议&#xff0c;过去这两个协议有两个不同的角色。PROFINET通常用于现场设备和本地控制器之间的实时数据通信。而OPC UA通常用于在本地控制器和更高级别的MES和SCADA系统之间进行通信。 OPC UA 网络架构 PROFINET网络由IO控制器和IO设备组成&…

【数据结构】什么是树?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 &#x1f4cc;树的定义 树(Tree)是n(n≥0)个结点的有限集.n0时称为空树. 在任意一颗非空树中: 有且仅有一个特定的称为根(Root)的结点;当n>1时,其余结点可分为m(m>0)个互…

Laravel框架使用phpstudy本地安装的composer用Laravel 安装器进行安装搭建

一、首先需要安装Laravel 安装器 composer global require laravel/installer 二、安装器安装好后&#xff0c;可以使用如下命令创建项目 laravel new sys 三、本地运行 php artisan serve 四、 使用Composer快速安装Laravel5.8框架 安装指定版本的最新版本&#xff08;推荐&a…

虚拟机磁盘扩容

https://www.xjx100.cn/news/528878.html?actiononClick https://blog.csdn.net/ytraister/article/details/129631653 PC电脑 VMware安装的linux CentOs7如何扩容磁盘&#xff1f; - 知乎 (zhihu.com) 解决虚拟机磁盘满了&#xff0c;无法上传文件&#xff0c;给虚拟机扩容问…

怎样写一个C语言程序计算以下问题?

怎样写一个C语言程序计算以下问题&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「C语言的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&#xff01…

Python 将RTF文件转为Word 、PDF、HTML

RTF也称富文本格式&#xff0c;是一种具有良好兼容性的文档格式&#xff0c;可以在不同的操作系统和应用程序之间进行交换和共享。有时出于不同项目的需求&#xff0c;我们可能需要将RTF文件转为其他格式。本文将介如何通过简单的Python代码将RTF文件转换为Word Doc/Docx、PDF、…