解锁互联网安全的新钥匙:JWT(JSON Web Token)

这篇具有很好参考价值的文章主要介绍了解锁互联网安全的新钥匙:JWT(JSON Web Token)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

一、JWT简介

1. 什么是JWT?

​编辑

2. JWT的工作原理

3.JWT如何工作的

4. JWT的优势

5. 在实际应用中使用JWT

6.传统Session和JWT认证的区别

6.1.session认证方式

6.2.JWT认证方式

7.基于Token的身份认证 与 基于服务器的身份认证 

二、JWT的结构

(1) Header

(2) Payload

(3) Signature

 三、JWT的使用

1.工具类

2.JWT的生成与解析

3.token刷新并延长默认有效时间

4.测试JWT的有效时间

5.模拟JWT令牌过期

四、案例讲解

1.后端编写

2.前端编写


前言

互联网安全一直是用户和开发者们关注的焦点。本文介绍了一种名为JWT(JSON Web Token)的新型安全传输标准,探讨了其工作原理、优势,并展示了在实际应用中的强大功能。无论你是一个前端开发者、后端工程师还是安全专家,这篇博客都将为你揭开JWT的神秘面纱,并带来惊喜和启发。

一、JWT简介

1. 什么是JWT?

JWT(JSON Web Token)是一种开放的标准(RFC 7519),用于在不同实体之间安全地传输、认证和验证数据。它使用 JSON 对象作为载荷(Payload),并使用数字签名或加密算法对其进行安全性保护。JWT通常由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

官网地址:https://jwt.io/introduction

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

2. JWT的工作原理

  • 头部(Header):头部包含了描述签名算法和加密算法等元数据的信息。它通常由两部分组成:令牌类型(typ)和加密算法(alg)。例如,{"typ": "JWT", "alg": "HS256"}表示该令牌使用HS256算法进行签名。

  • 载荷(Payload):载荷是真正存储数据的部分。它包含了一些声明(Claim),用于描述数据的一些属性和相关信息,比如用户ID、角色、过期时间等。载荷可以被加密或签名,但不能包含敏感信息。

  • 签名(Signature):签名是对头部和载荷进行数字签名以保证数据的完整性和安全性。签名需要使用到密钥,确保只有持有正确密钥的实体才能验证签名,并且防止数据篡改。

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

上述原理图描述: 

  1. 应用程序或客户端向授权服务器请求授权。
  2. 授予授权后,授权服务器将向应用程序返回访问令牌。
  3. 应用程序使用访问令牌访问受保护的资源(如 API)。

3.JWT如何工作的

在认证的时候,当用户用他们的凭证成功登录以后,一个JSON Web Token将会被返回。此后,token就是用户凭证了,你必须非常小心以防止出现安全问题。一般而言,你保存令牌的时候不应该超过你所需要它的时间。

无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中,用Bearer schema。

header应该看起来是这样的:

Authorization: Bearer

服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。

如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。

4. JWT的优势

  • 无状态性:由于JWT自包含了用户身份和相关信息,服务器无需存储会话信息或状态,因此可以轻松实现无状态的服务端架构。

  • 可扩展性和灵活性:JWT的载荷可以包含自定义的声明,根据实际需求灵活添加所需数据,并在验证时进行相应处理。

  • 跨平台和跨语言:JWT是基于标准的JSON格式,因此可以在不同平台和编程语言之间进行交互和解析,使得系统的集成更加容易。

  • 安全性:通过数字签名或加密,JWT可以确保数据的完整性、真实性和保密性。

5. 在实际应用中使用JWT

  • 用户认证:通过使用JWT作为身份验证机制,服务器可以验证用户的身份并可靠地提供受保护的资源。

  • 单点登录(SSO):用户在成功登录后,可以通过JWT在多个应用之间共享身份信息,无需再次登录。

  • 授权和权限管理:JWT携带了用户角色和权限等信息,在服务端可以轻松进行鉴权和权限控制。

6.传统Session和JWT认证的区别

6.1.session认证方式

http协议本身是一种无状态的协议,如果用户向服务器提供了用户名和密码来进行用户认证,下次请求时,用户还要再一次进行用户认证才行。因为根据http协议,服务器并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储─份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样应用就能识别请求来自哪个用户。

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

暴露的问题

  • 用户经过应用认证后,应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大;
  • 用户认证后,服务端做认证记录,如果认证的记录被保存在内存中的话,用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源。在分布式的应用上,限制了负载均衡器的能力。以此限制了应用的扩展能力;
  • session是基于cookie来进行用户识别,cookie如果被截获,用户很容易受到CSRF(跨站伪造请求攻击)攻击;
  • 在前后端分离系统中应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session每次携带sessionid到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。

6.2.JWT认证方式

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

 认证流程

  • 前端通过Web表单将自己的用户名和密码发送到后端的接口。该过程一般是HTTP的POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。
  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage(浏览器本地缓存)或sessionStorage(session缓存)上,退出登录时前端删除保存的JWT即可。
  • 前端在每次请求时将JWT放入HTTP的Header中的Authorization位。(解决XSS和XSRF问题)HEADER
  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确﹔检查Token是否过期;检查Token的接收方是否是自己(可选)
  • ·验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

7.基于Token的身份认证 与 基于服务器的身份认证 

7.1 基于服务器的身份认证

在讨论基于Token的身份认证是如何工作的以及它的好处之前,我们先来看一下以前我们是怎么做的:

HTTP协议是无状态的,也就是说,如果我们已经认证了一个用户,那么他下一次请求的时候,服务器不知道我是谁,我们必须再次认证

传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。

这种基于服务器的身份认证方式存在一些问题:

  • Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
  • Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
  • CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
  • CSRF : 用户很容易受到CSRF攻击。

7.2. JWT与Session的差异 相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。

Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。

Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

7.3. 基于Token的身份认证是如何工作的 基于Token的身份认证是无状态的,服务器或者Session中不会存储任何用户信息。

没有会话信息意味着应用程序可以根据需要扩展和添加更多的机器,而不必担心用户登录的位置。

虽然这一实现可能会有所不同,但其主要流程如下:

-用户携带用户名和密码请求访问 -服务器校验用户凭据 -应用提供一个token给客户端 -客户端存储token,并且在随后的每一次请求中都带着它 -服务器校验token并返回数据

注意:

-每一次请求都需要token -Token应该放在请求header中 -我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

7.4. 用Token的好处 - 无状态和可扩展性:Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。 - 安全:Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。即使在你的实现中将token存储到客户端的Cookie中,这个Cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话!

还有一点,token在一段时间以后会过期,这个时候用户需要重新登录。这有助于我们保持安全。还有一个概念叫token撤销,它允许我们根据相同的授权许可使特定的token甚至一组token无效。

7.5. JWT与OAuth的区别 -OAuth2是一种授权框架 ,JWT是一种认证协议 -无论使用哪种方式切记用HTTPS来保证数据的安全性 -OAuth2用在使用第三方账号登录的情况(比如使用weibo, qq, github登录某个app),而JWT是用在前后端分离, 需要简单的对后台API进行保护时使用。

二、JWT的结构

在其紧凑的形式中,JWT由以点(.)分隔的三个部分组成,它们是:

  • Header
  • Payload
  • Signature

类似于xxxx.xxxx.xxxx格式,真实情况如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

并且你可以通过官网JSON Web Tokens - jwt.io解析出三部分表示的信息( 可使用 JWT.io Debugger 来解码、验证和生成 JWT ):

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

(1) Header

报头通常由两部分组成: Token的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256或 RSA)。

例如:

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

最终这个 JSON 将由base64进行加密(该加密是可以对称解密的),用于构成 JWT 的第一部分,eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9就是base64加密后的结果。

(2) Payload

Token的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的语句。有三种类型的声明: registered claims, public claims, and private claims。

例如:

{
  "sub": "1234567890",// 注册声明
  "name": "John Doe",// 公共声明
  "admin": true // 私有声明
}

这部分的声明也会通过base64进行加密,最终形成JWT的第二部分eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
registered claims(注册声明)

这些是一组预定义的声明,它们  不是强制性的,而是推荐的 ,以  提供一组有用的、可互操作的声明 。

例如:

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

注意:声明名称只有三个字符,因为 JWT 意味着是紧凑的。

Public claims(公共的声明)

使用 JWT 的人可以随意定义这些声明(  可以自己声明一些有效信息如用户的id,name等,但是不要设置一些敏感信息,如密码 )。但是为了避免冲突,应该在 JWT注册表中定义它们,或者将它们定义为包含抗冲突名称空间的 URI。

Private claims(私人声明)

这些是创建用于在同意使用它们的各方之间共享信息的习惯声明,既不是注册声明,也不是公开声明(  私人声明是提供者和消费者所共同定义的声明 )。

注意:对于已签名的Token,这些信息虽然受到保护,不会被篡改,但任何人都可以阅读。除非加密,否则不要将机密信息放在 JWT 的有效负载或头元素中。

(3) Signature

要创建Signature,您必须获取编码的标头(header)、编码的有效载荷(payload)、secret、标头中指定的算法,并对其进行签名。

例如,如果您想使用 HMAC SHA256算法,签名将按以下方式创建:

HMACSHA256(
  base64UrlEncode(header) + "." +base64UrlEncode(payload),
  secret
  )

上面的JSON将会通过HMACSHA256算法结合secret进行加盐签名(私钥加密),其中header和payload将通过base64UrlEncode()方法进行base64加密然后通过字符串拼接 "." 生成新字符串,最终生成JWT的第三部分SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

 三、JWT的使用

1.工具类

在实际项目中一般会把JWT相关操作封装成工具类使用

package com.ctb.ssm.jwt;

import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * JWT验证过滤器:配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
 *
 */
public class JwtUtils {
	/**
	 * JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟
	 */
	public static final long JWT_WEB_TTL = 30 * 60 * 1000;

	/**
	 * 将jwt令牌保存到header中的key
	 */
	public static final String JWT_HEADER_KEY = "jwt";

	// 指定签名的时候使用的签名算法,也就是header那部分,jwt已经将这部分内容封装好了。
	private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
	private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙
	private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key

	static {
		byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
		JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
	}

	private JwtUtils() {
	}

	/**
	 * 解密jwt,获得所有声明(包括标准和私有声明)
	 * 
	 * @param jwt
	 * @return
	 * @throws Exception
	 */
	public static Claims parseJwt(String jwt) {
		Claims claims = Jwts.parser()
				.setSigningKey(JWT_KEY)
				.parseClaimsJws(jwt)
				.getBody();
		return claims;
	}

	/**
	 * 创建JWT令牌,签发时间为当前时间
	 * 
	 * @param claims
	 *            创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
	 * @param ttlMillis
	 *            JWT的有效时间(单位毫秒),当前时间+有效时间=过期时间
	 * @return jwt令牌
	 */
	public static String createJwt(Map<String, Object> claims, 
			long ttlMillis) {
		// 生成JWT的时间,即签发时间 2021-10-30 10:02:00 -> 30 10:32:00
	
		long nowMillis = System.currentTimeMillis();

		
		//链式语法:
		// 下面就是在为payload添加各种标准声明和私有声明了
		// 这里其实就是new一个JwtBuilder,设置jwt的body
		JwtBuilder builder = Jwts.builder()
				// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
				.setClaims(claims)
				// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
				// 可以在未登陆前作为身份标识使用
				.setId(UUID.randomUUID().toString().replace("-", ""))
				// iss(Issuser)签发者,写死
				.setIssuer("ctb")
				// iat: jwt的签发时间
				.setIssuedAt(new Date(nowMillis))
				// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
				// .setSubject("{}")
				// 设置签名使用的签名算法和签名使用的秘钥
				.signWith(SIGNATURE_ALGORITHM, JWT_KEY)
				// 设置JWT的过期时间
				.setExpiration(new Date(nowMillis + ttlMillis));

		return builder.compact();
	}

	/**
	 * 复制jwt,并重新设置签发时间(为当前时间)和失效时间
	 * 
	 * @param jwt
	 *            被复制的jwt令牌
	 * @param ttlMillis
	 *            jwt的有效时间(单位毫秒),当前时间+有效时间=过期时间
	 * @return
	 */
	public static String copyJwt(String jwt, Long ttlMillis) {
		//解密JWT,获取所有的声明(私有和标准)
		//old
		Claims claims = parseJwt(jwt);

		// 生成JWT的时间,即签发时间
		long nowMillis = System.currentTimeMillis();

		// 下面就是在为payload添加各种标准声明和私有声明了
		// 这里其实就是new一个JwtBuilder,设置jwt的body
		JwtBuilder builder = Jwts.builder()
				// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
				.setClaims(claims)
				// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
				// 可以在未登陆前作为身份标识使用
				//.setId(UUID.randomUUID().toString().replace("-", ""))
				// iss(Issuser)签发者,写死
				// .setIssuer("zking")
				// iat: jwt的签发时间
				.setIssuedAt(new Date(nowMillis))
				// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
				// .setSubject("{}")
				// 设置签名使用的签名算法和签名使用的秘钥
				.signWith(SIGNATURE_ALGORITHM, JWT_KEY)
				// 设置JWT的过期时间
				.setExpiration(new Date(nowMillis + ttlMillis));
		return builder.compact();
	}
}




JWT工具类中的方法注释

  1. public static final long JWT_WEB_TTL = 30 * 60 * 1000;:定义了一个常量,表示Web应用中JWT的有效时间,单位为毫秒,默认值为30分钟。

  2. public static final String JWT_HEADER_KEY = "jwt";:定义了一个常量,表示在header中保存JWT的键名,默认为"jwt"。

  3. private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;:定义了一个静态常量,表示签名算法,这里使用的是HS256。

  4. private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";:定义了一个静态常量,表示JWT的密钥。

  5. static {...}:这是一个静态代码块,用于初始化JWT的密钥。首先将JWT的密钥从Base64格式解码为字节数组,然后使用这个字节数组创建一个SecretKey对象。

  6. public static Claims parseJwt(String jwt) {...}:这是一个静态方法,用于解析JWT,返回一个Claims对象,其中包含了JWT的所有声明。

  7. public static String createJwt(Map<String, Object> claims, long ttlMillis) {...}:这是一个静态方法,用于创建JWT令牌。首先根据传入的claims和ttlMillis计算出JWT的签发时间和过期时间,然后使用Jwts的builder模式构建JWT,并设置签名算法和密钥,最后返回生成的JWT字符串。

  8. public static String copyJwt(String jwt, Long ttlMillis) {...}:这是一个静态方法,用于复制JWT。首先调用parseJwt方法解析传入的JWT,获取其所有的声明,然后使用这些声明和传入的ttlMillis创建一个新的JWT,并返回。

2.JWT的生成与解析

  • 通过Java代码实现JWT的生成( 使用的是JJWT框架 )

先导入JJWT的依赖(JJWT是JWT的框架)

 <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

测试代码如下:

private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

	@Test
	public void test1() {// 生成JWT
		
		//JWT Token=Header.Payload.Signature
		//头部.载荷.签名
		//Payload=标准声明+私有声明+公有声明
		
		//定义私有声明
		Map<String, Object> claims = new HashMap<String, Object>();
		claims.put("username", "ctb");
		claims.put("age", 23);
		
	    //TTL:Time To Live
		String jwt = JwtUtils.createJwt(claims, JwtUtils.JWT_WEB_TTL);
		System.out.println(jwt);

		//获取Payload(包含标准和私有声明)
		Claims parseJwt = JwtUtils.parseJwt(jwt);
		for (Map.Entry<String, Object> entry : parseJwt.entrySet()) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

运行结果: 

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

通过官网进行解码: 

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

  • 通过Java代码实现JWT的解码( 使用的是JJWT框架 )

测试代码如下:

@Test
	public void test2() {// 解析oldJwt
		//io.jsonwebtoken.ExpiredJwtException:JWT过期异常
		//io.jsonwebtoken.SignatureException:签名异常
		//eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjE3MjcsImlhdCI6MTYzNTU1OTkyNywiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ9.dUR-9JUlyRdoYx-506SxXQ3gbHFCv0g5Zm8ZGzK1fzw
		String newJwt="eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjdGIiLCJleHAiOjE2OTcyMDYzNDYsImlhdCI6MTY5NzIwNDU0NiwiYWdlIjoyMywianRpIjoiYmQ2OTNiYWIxZDAxNDMwMWExMmNjOGMyNDJkNDdmOGEiLCJ1c2VybmFtZSI6ImN0YiJ9.lWtz13pyHJUYWd2OrSLE-MGYmHqzvACnAtIJOUFS1UM";
//		String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjE3MjcsImlhdCI6MTYzNTU1OTkyNywiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ9.dUR-9JUlyRdoYx-506SxXQ3gbHFCv0g5Zm8ZGzK1fzw";
		Claims parseJwt = JwtUtils.parseJwt(newJwt);
		for (Map.Entry<String, Object> entry : parseJwt.entrySet()) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

运行结果: 

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

3.token刷新并延长默认有效时间

测试代码如下:

@Test
	public void test3() {// 复制jwt,并延时30分钟
		String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY2MjM0Njg3MSwiaWF0IjoxNjYyMzQ1MDcxLCJhZ2UiOjE4LCJqdGkiOiI4YjllNzc3YzFlMDM0MjViYThmMDVjNTFlMTU3NDQ1MiIsInVzZXJuYW1lIjoienNzIn0.UWpJxPxwJ09PKxE2SY5ME41W1Kv3jP5bZGKK-oNUDuM";
		//String newJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDU3NTM2NTUsImlhdCI6MTYwNTc1MTg1NSwiYWdlIjoxOCwianRpIjoiYmNmN2Q1MzQ2YjE3NGU2MDk1MmIxYzQ3ZTlmMzQyZjgiLCJ1c2VybmFtZSI6InpzcyJ9.m1Qn84RxgbKCnsvrdbbAnj8l_5Jwovry8En0j4kCxhc";
		//String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjI5MDMzNjAsImlhdCI6MTU2MjkwMTU2MCwiYWdlIjoxOCwianRpIjoiZDVjMzE4Njg0MDcyNDgyZDg1MDE5ODVmMDY3OGQ4NjkiLCJ1c2VybmFtZSI6InpzcyJ9.XDDDRRq5jYq5EdEBHtPm7GcuBz4S0VhDTS1amRCdf48";
		String newJwt = JwtUtils.copyJwt(oldJwt, JwtUtils.JWT_WEB_TTL);
		System.out.println(newJwt);
		Claims parseJwt = JwtUtils.parseJwt(newJwt);
		for (Map.Entry<String, Object> entry : parseJwt.entrySet()) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

复制并延长JWT的有效期,并输出解析后的JWT的相关信息。 

运行结果:

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

4.测试JWT的有效时间

测试代码如下:

@Test
	public void test4() {// 测试JWT的有效时间
		Map<String, Object> claims = new HashMap<String, Object>();
		claims.put("username", "zss");
		String jwt = JwtUtils.createJwt(claims, 3 * 1000L);
		System.out.println(jwt);
		Claims parseJwt = JwtUtils.parseJwt(jwt);
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

它创建了一个带有指定声明信息和有效期的JWT,并输出解析后的JWT的令牌签发时间和过期时间。在本例中,JWT的有效期为3秒。

 运行结果:

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

5.模拟JWT令牌过期

测试代码如下:

@Test
	public void test5() {// 三秒后再解析上面过期时间只有三秒的令牌,因为过期则会报错io.jsonwebtoken.ExpiredJwtException
		//String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjMzODIsImlhdCI6MTYzNTU2MTU4MiwiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ1.F4pZFCjWP6wlq8v_udfhOkNCpErF5QlL7DXJdzXTHqE";
		String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY2MjM0Njg3MSwiaWF0IjoxNjYyMzQ1MDcxLCJhZ2UiOjE4LCJqdGkiOiI4YjllNzc3YzFlMDM0MjViYThmMDVjNTFlMTU3NDQ1MiIsInVzZXJuYW1lIjoienNzIn9.UWpJxPxwJ09PKxE2SY5ME41W1Kv3jP5bZGKK-oNUDuM";
		Claims parseJwt = JwtUtils.parseJwt(oldJwt);
		// 过期后解析就报错了,下面代码根本不会执行
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

测试在令牌过期后解析该令牌会发生什么情况。它试图解析一个过期的JWT,并演示了当JWT过期时会抛出ExpiredJwtException异常。 

运行结果:

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

四、案例讲解

1.后端编写

JWT是跨语言的,自然也涉及到跨域问题,在我们的过滤器里面加入需允许JWT的跨域请求

CorsFilter 

package com.ctb.ssm.util;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 配置tomcat允许跨域访问
 * 
 * @author Administrator
 *
 */
public class CorsFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
		HttpServletRequest req = (HttpServletRequest) servletRequest;
		// Access-Control-Allow-Origin就是我们需要设置的域名
		// Access-Control-Allow-Headers跨域允许包含的头。
		// Access-Control-Allow-Methods是允许的请求方式
		httpResponse.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
		httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
		
		//允许客户端发一个新的请求头jwt
		httpResponse.setHeader("Access-Control-Allow-Headers","responseType,Origin,X-Requested-With, Content-Type, Accept, jwt");
		//允许客户端处理一个新的响应头jwt
		httpResponse.setHeader("Access-Control-Expose-Headers", "jwt,Content-Disposition");
		
		//httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
		//httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
		
		// axios的ajax会发两次请求,第一次提交方式为:option,直接返回即可
		if ("OPTIONS".equals(req.getMethod())) {
			return;
		}
		filterChain.doFilter(servletRequest, servletResponse);
	}
	@Override
	public void destroy() {

	}
}

在web.xml进行配置过滤器

  <!--CrosFilter跨域过滤器-->
  <filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.ctb.ssm.util.CorsFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

注意:

我们在登陆后通过JWT保存我们的用户数据,首先我们在这就需要排除登录通过JWT认证

JWTFilter

package com.ctb.ssm.jwt;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.jsonwebtoken.Claims;

/**
 * * JWT验证过滤器,配置顺序 :CorsFilter-->JwtFilter-->struts2中央控制器
 * 
 * @author biao
 *
 */

public class JwtFilter implements Filter {

	// 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
	private static String EXCLUDE = "^/user/userLogin?.*$";

	private static Pattern PATTERN = Pattern.compile(EXCLUDE);

	private boolean OFF = true;// true关闭jwt令牌验证功能

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void destroy() {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		//获取当前请求路径。只有登录的请求路径不进行校验之外,其他的URL请求路径必须进行JWT令牌校验
		//http://localhost:8080/ssh2/bookAction_queryBookPager.action
		//req.getServletPath()==/bookAction_queryBookPager.action
		String path = req.getServletPath();
		if (OFF || isExcludeUrl(path)) {// 登陆直接放行
				chain.doFilter(request, response);
				return;
		}

		// 从客户端请求头中获得令牌并验证
		//token=头.载荷.签名
		String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
		Claims claims = this.validateJwtToken(jwt);
		//在这里请各位大哥大姐从JWT令牌中提取payload中的声明部分
		//从声明部分中获取私有声明
		//获取私有声明中的User对象 -> Modules
		Boolean flag=false;
		if (null == claims) {
			// resp.setCharacterEncoding("UTF-8");
			resp.sendError(403, "JWT令牌已过期或已失效");
			return;
		} else {
			
			//1.获取已经解析后的payload(私有声明)
			//2.从私有声明中当前用户所对应的权限集合List<String>或者List<Module>
			//3.循环权限(Module[id,url])
			// OK,放行请求 chain.doFilter(request, response);
			// NO,发送错误信息的JSON
			// ObjectMapper mapper=new ObjectMapper()
			// mapper.writeValue(response.getOutputStream(),json)
			
			String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL);
			resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt);
			chain.doFilter(request, response);
		}
	}

	/**
	 * 验证jwt令牌,验证通过返回声明(包括公有和私有),返回null则表示验证失败
	 */
	private Claims validateJwtToken(String jwt) {
		Claims claims = null;
		try {
			if (null != jwt) {
				//该解析方法会验证:1)是否过期 2)签名是否成功
				claims = JwtUtils.parseJwt(jwt);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return claims;
	}

	/**
	 * 是否为排除的URL
	 * 
	 * @param path
	 * @return
	 */
	private boolean isExcludeUrl(String path) {
		Matcher matcher = PATTERN.matcher(path);
		return matcher.matches();
	}

	// public static void main(String[] args) {
	// String path = "/sys/userAction_doLogin.action?username=zs&password=123";
	// Matcher matcher = PATTERN.matcher(path);
	// boolean b = matcher.matches();
	// System.out.println(b);
	// }

}

private boolean OFF = false;我们在开发过程中可以通过这个属性来决定我们要不要使用JWT,如果我们开发过程都需要JWT的话测试是非常麻烦的。  

在web.xml也需配置

<!--JwtFilter-->
  <filter>
    <filter-name>jwtFilter</filter-name>
    <filter-class>com.ctb.ssm.jwt.JwtFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>jwtFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

UserController

在controller层编写保存JWT的方法并回显给前端

/**
     * 登录
     * @param userVo
     * @param response
     * @return
     */
    @RequestMapping("/userLogin")
    @ResponseBody
    public JsonResponseBody<?> userLogin(UserVo userVo, HttpServletResponse response){
        if(userVo.getUsername().equals("admin")&&userVo.getPassword().equals("123")){
            //私有要求claim
            Map<String,Object> json=new HashMap<String,Object>();
            json.put("username", userVo.getUsername());
            //生成JWT,并设置到response响应头中
            String jwt=JwtUtils.createJwt(json, JwtUtils.JWT_WEB_TTL);
            response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
            return new JsonResponseBody<>("用户登陆成功!",true,0,null);
        }else{
            return new JsonResponseBody<>("用户名或密码错误!",false,0,null);
        }
    }

2.前端编写

在这里我们也是结合上篇博客Vuex存值取值与异步请求处理继续编写的

在store/state.js中定义jwt变量

export default{
  jwt:'',
}

在store/mutations.js编写设置jwt的方法

export default{
    //state指的是state.js文件中导出的对象
    //payload是vue文件传递过来的参数
  setJwt: (state, payload) => {
      state.jwt = payload.jwt;
    }

}

在store/getters.js编写获取jwt的方法

export default{
 
  getJwt: (state) => {
      return state.jwt
    }

}

 登录过后请求主页会将开始后端响应的JWT保存到Vuex,下次发送请求的使用就会带上这个JWT,后端校验如果不是登录请求又没有JWT将不会“放行请求” 

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

结果:

复制相同链接重新加载页面,将不会出现数据库左侧列表信息。

解锁互联网安全的新钥匙:JWT(JSON Web Token),Vue+Element UI,json,vue.js,前端,javascript,elementui,java,maven

我们的JWT生效了,在第二次请求中没有带JWT是过不了后端请求的!若是我们没有借助JWT认证,那我们就可以一直发请求,这是不安全的!文章来源地址https://www.toymoban.com/news/detail-716909.html

到了这里,关于解锁互联网安全的新钥匙:JWT(JSON Web Token)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 互联网+医疗|如何满足各项安全合规要求

    互联网+医疗背景下,如何有针对性地规范医疗健康App运营,堵住个人隐私信息安全漏洞,是一个亟待解决的行业问题,也是一个数字时代的公共安全问题。此前,多款医疗健康类App就因过度收集个人信息被监管通报。 与电子商务、消费金融、音视频平台等其他领域相比,医

    2023年04月17日
    浏览(57)
  • 网络安全之互联网暴露资产端口

    互联网暴露资产因直接向公众互联网开放,极易遭受来自外部组织或人员的入侵与攻击,是风险管控的高危区域。 作为企业的安全管理,互联网暴露资产的管理是非常重要的一环。应该建立规范的流程严控互联网暴露端口的审批,对互联网暴露出口应尽量缩减收敛减少暴露面

    2024年02月08日
    浏览(55)
  • Ping32助力互联网行业安全、合规发展

    数据是当今社会最宝贵的资源之一,其价值已经被广泛认可。随着互联网技术的不断发展,数据开始在商业、科技、医疗、金融等领域得到广泛应用,为社会的发展和进步带来了巨大的贡献。与此同时,数据泄露和滥用的风险也在不断增加,给互联网行业带来了巨大的安全挑

    2024年02月14日
    浏览(37)
  • 某互联网银行绿色金融背后的“安全秘诀”

    ​随着银保监会出台《银行业保险业绿色金融指引》、人民银行牵头制定《G20转型金融框架》的发布,金融行业正在持续加大对绿色金融支持力度。某互联网银行为了响应号召,采用数字化无纸化办公,线上零接触服务减少大量碳排放,成为 国内首家实现经营活动全面碳中和

    2024年02月09日
    浏览(46)
  • 智能终端安全:应用安全技术—移动互联网信息安全解决方案(下)

    手机作为一个随身可移动的信息承载终端,面临着各种不同使用场景,灵活的可配置的信息安全策略和稳妥可靠的管理非常必要, 需要提供必要的云端安全管控能力。 此处提到的云端安全管控平台, 包括运营商针对移动互联网需求的网络安全设计和运营商安全能力开放、可

    2024年02月05日
    浏览(65)
  • 智能终端安全:应用安全技术—移动互联网信息安全解决方案(上)

    移动互联网智能终端信息安全是一个整体的系统性课题,从技术角度来看,涉及到 终端硬件架构、内核和终端操作系统、应用等各个层面 。 同时,信息安全又是和用户自身需求紧密相关的需求驱动型课题,从用户角度来看,公众用户可能更关心通信安全和费用、反病毒和防

    2024年02月03日
    浏览(52)
  • 《互联网的世界》第六讲-去中心化和安全

    互联网构建于开放互联的中立原则之上,公平接入,数据互联互通,流量被无差别对待,这意味着互联网本质上是匿名,去中心的,这与我们的现实世界完全不同。 但互联网上的主流业务却是 c/s 产销模式,试图在互联网世界复刻现实世界。我们对比开放互联的中立原则和现

    2024年03月19日
    浏览(89)
  • 2023年工业互联网(网络安全)赛题AI解析

    debian中设置root账户口令,开启口令复杂度检 查,至少包含小写字母、大写字母、数字、特殊字符4类字符,设置最小口令长度为8位,且新口令必须与旧口令有3位不同 要设置root账户口令并开启口令复杂度检查,您可以按照以下步骤进行操作: 打开终端,以root身份登录或者使

    2024年02月08日
    浏览(51)
  • 互联网发展历程:保护与隔离,防火墙的安全壁垒

    互联网的快速发展,不仅带来了便利和连接,也引发了越来越多的安全威胁。在数字时代,保护数据和网络安全变得尤为重要。然而,在早期的网络中,安全问题常常让人担忧。 安全问题的困扰:网络威胁日益增加 随着互联网的普及,网络安全威胁也不断增加。恶意软件、

    2024年02月12日
    浏览(51)
  • 聚焦云原生安全|如何为5G边缘云和工业互联网应用筑牢安全防线

    9月22日 ,2023年中国信息通信业发展高层论坛5G+工业互联网分论坛在北京顺利举办。 作为 国内云原生安全领导厂商 , 安全狗 受邀出席此次活动。 厦门服云信息科技有限公司(品牌名:安全狗)成立于2013年,致力于提供云安全、(云)数据安全领域相关产品、服务及解决方

    2024年02月08日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包