JWT技术

news/2024/11/14 15:38:49/

JWT

一、 JWT 实现无状态 Web 服务

1、什么是有状态

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,无法进行水平扩展
  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

2、什么是无状态

服务器不需要记录客户端的状态信息,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

带来的好处是什么呢?

  • 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩
  • 减小服务端存储压力

3、如何实现无状态

无状态登录的流程:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务的对token进行解密,判断是否有效。

流程图:

​ 客户端请求登录,登录之后颁发凭证

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用:JWT + RSA非对称加密

4、JWT简介

JWT全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

JWT包含三部分数据:

  • Header:头部,通常头部有两部分信息:

    • 声明类型,这里是JWT 自描述信息

    我们会对头部进行base64编码,得到第一部分数据 base64编码和解码的

  • Payload:载荷,就是有效数据,一般包含下面信息:

    • 用户身份信息(注意,这里因为采用base64编码,可解码是可逆的,因此不要存放敏感信息)
    • 注册声明:如token的签发时间,过期时间,签发人等 这部分内容 好比身份证的信息

    这部分也会采用base64编码,得到第二部分数据

  • Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上密钥(secret)(不要泄漏,最好周期性更换),通过加密算法(不可逆的)生成一个签名。用于验证整个数据完整和可靠性。

生成的数据格式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLsk4cfM-1679728889833)(assets\image-20210824192550245.png)]

可以看到分为3段,每段就是上面的一部分数据

5、JWT交互流程

步骤翻译:

  • 1、用户登录
  • 2、服务的认证,通过后生成jwt
  • 3、将生成的jwt返回给浏览器
  • 4、用户每次请求携带jwt
  • 5、服务端利用公钥解读jwt签名,判断签名有效后,从Payload中获取用户信息
  • 6、处理请求,返回响应结果

二、nimbus-jose-jwt 库

1、进入依赖

nimbus-jose-jwt、jose4j、java-jwt 是几个 Java 中常见的操作 JWT 的库

nimbus-jose-jwt 官网:https://connect2id.com/products/nimbus-jose-jwt

所需坐标

    <dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId><version>9.11.1</version></dependency>

2、核心 API

2.1、加密过程

  • 在 nimbus-jose-jwt 中,使用 Header 类代表 JWT 的头部,不过,Header 类是一个抽象类,我们使用的是它的子类 JWSHeader

    创建头部对象:

     @Testpublic void createToken(){//创建头部对象JWSHeader jwsHeader =new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法.type(JOSEObjectType.JWT) // 静态常量.build();System.out.println(jwsHeader);}
    

    你可以通过 .toBase64URL() 方法求得头部信息的 Base64 形式(这也是 JWT 中的实际头部信息):

  • 使用 Payload 类的代表 JWT 的荷载部分

    创建荷载部对象:

        @Testpublic void createToken(){//创建头部对象JWSHeader jwsHeader =new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法.type(JOSEObjectType.JWT) // 静态常量.build();System.out.println(jwsHeader);//创建载荷Payload payload = new Payload("hello world");System.out.println(payload);}
    

    你可以通过 .toBase64URL() 方法求得荷载部信息的 Base64 形式(这也是 JWT 中的实际荷载部信息):

  • 签名部分

    ​ 签名部分没有专门的类表示,签名部分并非你自己创建出来的,而是靠 头部 + 荷载部 + 加密算法 算出来的

    ​ nimbus-jose-jwt 专门提供了一个签名器 JWSSigner ,用来参与到签名过程中。密钥就是在创建签名器的时候指定的:

    JWSSigner jwsSigner = new MACSigner("密钥");  //MACSigner()中要指定一个密钥
    

    最终,整个 JWT 由一个 JWSObject 对象表示:

    JWSObject jwsObject = new JWSObject(jwsHeader, payload);
    // 进行签名(根据前两部分生成第三部分)
    jwsObject.sign(jwsSigner);
    

    我们最终要的是 JWT 字符串,而不是对象,这里接着对代表 JWT 的 JWSObject 对象调用 .serialize() 方法即可:

    String token = jwsObject.serialize();
    

    完整示例:

      @Testpublic void createToken() throws JOSEException {//创建头部对象JWSHeader jwsHeader =new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法.type(JOSEObjectType.JWT) // 静态常量.build();//创建载荷Payload payload = new Payload("hello world");//创建签名器JWSSigner jwsSigner = new MACSigner("woniu");//woniu为密钥//创建签名JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷jwsObject.sign(jwsSigner);//再+签名部分//生成token字符串String token = jwsObject.serialize();System.out.println(token);}
    

如果出现:com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits异常,是因为密钥的长度不够增加密钥长度即可

2.2、 解密

反向的解密和验证过程核心 API 就 2 个:JWSObject 的静态方法 parse 方法和验证其 JWSVerifier 对象。

如果你想直接验证 JWSObject 对象的合法性,你需要创建一个 JWSVerifier 对象。

//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("密钥");//密钥要和加密时的相同

然后直接调用 jwsObject 对象的 verify 方法:

if (!jwsObject.verify(jwsVerifier)) {throw new RuntimeException("token 签名不合法!");
}

三、token续期

在实际的开发中,token不可能一直有效,比如30分钟内一次都没有进行操作,则认证过期,需要重新登录,如果一直在进行请求访问则token一直有效,直到上一次访问距离下一次访问的时间超过了30分钟,则认证过期。

springsecurity整合JWT:

@Component
public class JWTfilter extends OncePerRequestFilter {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Autowired(required = false)private SecurityLoginService securityLoginService;@SneakyThrows@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,FilterChain filterChain)throws ServletException, IOException {//功能点1:在请求头拿到jwtString jwt = httpServletRequest.getHeader("jwt");if (jwt == null) {//放给security 其他过滤器,该方法不做处理filterChain.doFilter(httpServletRequest, httpServletResponse);return;}// 功能点2:jwt不合法if (!JWTUtil.decode(jwt)) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}//功能点3 获取jwt的用户信息Map payLoad = JWTUtil.getPayload(jwt);String username = (String) payLoad.get("username");//拿到redis的jwtString redisJWT = redisTemplate.opsForValue().get("jwt:" + username);//判断redis是否有该jwtif (redisJWT == null) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}if (!jwt.equals(redisJWT)) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}//给redis 的jwt续期redisTemplate.opsForValue().set("jwt:" + username, jwt, 30,TimeUnit.MINUTES);//获取用户名,密码,权限UserDetails userDetails = securityLoginService.loadUserByUsername(username);// 获取用户信息 生成security容器凭证UsernamePasswordAuthenticationToken upa =new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());//放入凭证SecurityContextHolder.getContext().setAuthentication(upa);// 本方法共功能执行完了,交给下一个过滤器filterChain.doFilter(httpServletRequest, httpServletResponse);}
}//前后端项目中要禁用掉session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
在securityConfig 类注入
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);

http://www.ppmy.cn/news/315442.html

相关文章

JTS简介

地理坐标系 球面坐标系&#xff0c;以度为单位&#xff0c;比如WGS84等等投影坐标系&#xff0c;以米为单位&#xff0c;比如墨卡托投影&#xff0c;高斯投影&#xff0c;UTM投影等等 SRID 国际标准下坐标系唯一ID&#xff0c;比如WGS84 4326&#xff0c;墨卡托投影 3857 …

【Hadoop】环境准备

克隆虚拟机修改克隆虚拟机的静态IP修改主机名关闭防火墙创建jzt用户配置jzt用户具有root权限 注意 : 在克隆之前,母机需要设置好hosts映射,不然到时候每台主机去配置主机映射较麻烦 母机的主机映射文件hosts: 克隆虚拟机 修改克隆虚拟机的静态IP 将ip改了,并将硬件地址粘贴过…

史上最详细的安装Kali-linux教程(附视频教程)

之前不少人问kali怎么安装&#xff0c;今天就发一篇利用VM虚拟机安装kali的详细教程&#xff0c;每一步都截图了&#xff0c;让大家尽可能的清楚每一步的操作。 1.2 使用 VM 虚拟机安装 Kali 1.2.1 官方下载 Kali Linux 官方网址&#xff1a;http://www.Kali.org 下载方式分…

传感器是指纹识别产品的数据入口

苹果公司对指纹识别传感器供应商美国AuthenTec公司的收购&#xff0c;开启了指纹识别在智能手机领域大规模应用的先河。 华为Mate 7手机的热卖正式引爆了指纹识别在中高档智能手机中的普及&#xff0c;其准确快速的识别体验让瑞典Fingerprint Cards AB(FPC)公司一炮而红。 成为…

解决联通手机营业厅部分页面无法打开(打开空白 进不去 闪退 排版错乱)的问题.2021-02-26

如果手机系统的版本低于 安卓5 ,不用往下看了,换个手机吧.方法只适合安卓5及更高版本的系统 一.故障复现 使用的设备: 酷派锋尚pro2全网通版(系统UI:coolui8.0,安卓版本5.1.1) 出现故障的软件: 联通手机营业厅8.0102(其它版本也有这个问题) 打开手厅首页上的 剩余话费 或…

酷派+乐视能否再造一个“苹果生态”

6月30日&#xff0c;乐视创始人贾跃亭在微博称&#xff1a;“生态化反&#xff0c;开放共享。乐视移动智能公司成为酷派第二大股东&#xff0c;开放闭环的乐视生态将推动酷派快速进入手机生态时代&#xff0c;酷派深厚的产研供销、售后能力与乐视强化反&#xff0c;全流程共生共…

一款自动生成CRUD代码的自定义Starter

一个基础web的spring-boot-starter框架 web-spring-boot-starter Gitee仓库链接&#xff1a;https://gitee.com/suhuamo/web-spring-boot-starter Github仓库链接&#xff1a;https://github.com/suhuamo/web-spring-boot-starter 前言 对应一个web项目的开发&#xff0c;数据…

setState详解

this. setState( [partialState], [callback]) 1.[partialState] :支持部分状态更改 this, setState({ x:100 //不论总共有多少状态&#xff0c;我们只修改了x&#xff0c;其余的状态不动 });callback :在状态更改/视图更新完毕后触发执行&#xff0c;也可以说只要执行了setS…