一、Jwt简介
1.1 Jwt是什么
JWT,全称 JSON Web Token,是一种紧凑的、自包含的标准,用于在不同系统之间安全地传输信息。JWT 是一个开放标准(RFC 7519),它定义了一种简洁而独立的方法,用于在各方之间作为 JSON 对象安全地传输信息。通常,JWT 用于身份验证和授权,尤其在网络应用程序和服务之间。
1.2 为什么使用Jwt
使用JWT(JSON Web Token)有许多优点,其中包括以下几个主要原因:
- 轻量且自包含:JWT 是一种紧凑的数据格式,易于传输和处理。它包含了所有必要的信息,不需要在服务器端保留会话状态,因为所有的信息都存储在令牌中。这使得它特别适合用于分布式系统和跨域应用之间的通信。
- 无状态性:JWT 是无状态的,服务器不需要在处理请求时保留任何会话数据。这降低了服务器的负担,并有助于构建可伸缩的应用程序。
- 安全性:JWT 可以使用密钥进行签名,以验证令牌的完整性。只有拥有正确密钥的人才能生成有效的签名。这有助于防止令牌被篡改。此外,JWT 还可以进行加密,以保护负载中的敏感数据。
- 广泛支持:JWT 是一个开放标准,因此在不同的编程语言和平台上都有广泛的支持。这意味着您可以在各种技术栈中使用JWT,并且不受特定编程语言的限制。
- 适用于身份验证和授权:JWT 可以用于用户身份验证,允许用户在不再次提供凭证的情况下访问受保护的资源。它还可以包含用户的权限信息,用于授权访问。这使得JWT非常适合构建单点登录(SSO)系统和授权解决方案。
- 跨域应用支持:JWT 可以轻松用于跨不同域、不同应用程序之间的身份验证和数据传输。它允许不同系统之间共享用户身份和权限信息,而不需要复杂的集成或会话共享机制。
- 可自定义:JWT 的负载部分是自定义的,您可以在其中包含所需的声明信息。这使得JWT非常灵活,可以满足各种应用程序的需求。
尽管JWT具有这些优点,但也需要小心使用。密钥管理和令牌的保护非常关键,因为泄露密钥或令牌可能导致安全风险。此外,JWT 不适合存储大量敏感信息,因为它是基于 Base64 编码的,不具备加密级别的安全性。在设计和实施JWT时,需要综合考虑这些因素。
1.3 Jwt的工作原理
JWT(JSON Web Token)是一种用于在网络应用之间安全传输信息的开放标准(RFC 7519)。它工作的原理可以分为三个主要步骤:创建令牌、发送令牌、验证令牌。
1.创建令牌(Token Creation):
- Header(头部):JWT以Base64编码的JSON对象开始,其中包含了描述JWT的元数据,例如算法(HMAC SHA256或RSA等)。
- Payload(负载):接下来是包含声明的有效负载。声明是有关实体(通常是用户)以及关于令牌本身的信息。有效负载包括标准声明(例如,发行人、受众、过期时间)和自定义声明。有效负载也以Base64编码的JSON对象形式存在。
- Signature(签名):为了保证令牌的完整性,有效负载和头部通常会使用密钥进行签名。签名的过程会根据头部中指定的算法进行计算。签名部分是使用算法和密钥将Base64编码的头部和有效负载连接在一起生成的签名。
- 最终,JWT 由头部、有效负载和签名三个部分组成,它们以点号分隔构成一个字符串,如下所示:
header.payload.signature
。2.发送令牌(Token Transmission)
- 一旦JWT被创建,它可以通过HTTP请求的标头(通常是Authorization标头)或作为参数附加到URL中进行传输。JWT通常作为Bearer令牌发送,这意味着请求方需要在HTTP标头中包含一个令牌,格式为
Bearer <token>
。3.验证令牌(Token Verification):
- 接收方(通常是服务器)接收JWT后,首先需要解析JWT,通常通过分割字符串并解码Base64编码的头部和有效负载来完成。
- 接收方验证签名,确保令牌在传输过程中没有被篡改。这是通过使用在令牌创建时使用的密钥来计算新的签名并将其与接收到的签名进行比较来完成的。
- 接收方检查有效负载中的声明,例如过期时间,以确保令牌仍然有效。
- 如果一切正常,接收方信任令牌,允许用户访问所请求的资源或执行相应的操作。
总之,JWT的工作原理基于创建、发送和验证令牌。它允许在不需要维护会话状态的情况下,安全地传输信息,并在各种应用程序和服务之间实现身份验证和授权。密钥的安全管理对JWT的安全性至关重要,因为泄露密钥可能导致令牌被篡改。
1.4 Jwt的组成
JSON Web Tokens (JWT)时,JWT通常由三个部分组成,它们使用点号(.)分隔开。这三个部分是:
- Header(头部):包含了令牌的元数据,通常包括令牌类型和所使用的签名算法。
- Payload(载荷):包含了声明,用于在令牌中包含有关实体(例如用户)和其他数据的信息。声明分为三种类型:注册声明、公共声明和私有声明。
- Signature(签名):用于验证令牌的完整性和真实性,以确保令牌在传输过程中没有被篡改。
这三个部分组合在一起构成了JWT,它通常用于身份验证和授权等场景。
1.5 Jwt的验证过程
JWT(JSON Web Token)的验证过程通常涉及以下步骤:
- 解析JWT:首先,你需要将JWT令牌分解成其三个部分:头部(Header)、载荷(Payload)和签名(Signature)。这通常涉及将JWT令牌中的Base64编码部分解码,并分隔它们以获取明文数据。
- 验证头部:在验证JWT时,你应该首先检查头部部分,确保它包含正确的算法(通常是HMAC SHA256或RSA等)。此步骤用于验证签名的算法是否与你的预期一致。
- 获取密钥:根据验证头部的结果,你需要获取用于验证JWT签名的密钥。这个密钥可以是对称密钥(用于HMAC算法)或公钥(用于RSA算法)。密钥通常是预先共享的或从信任的身份提供者处获取的。
- 验证签名:使用得到的密钥对JWT令牌中的签名部分进行验证。如果你在头部指定了HMAC SHA256算法,那么你需要使用相同的密钥对载荷和头部的内容进行签名,并将结果与JWT令牌中的签名部分进行比较。如果它们匹配,说明JWT是有效的。
- 验证有效期:检查JWT令牌中的"exp"(过期时间)声明,确保令牌尚未过期。如果令牌已过期,它将被视为无效。
- 验证其他声明:根据你的需求,你可以验证其他JWT声明,例如"iss"(发行者)、"aud"(受众)、"sub"(主题)等。这些验证步骤可以根据你的具体需求进行定制。
- 成功验证:如果JWT通过了所有的验证步骤,那么它被认为是有效的,你可以信任其内容,例如载荷中的用户标识信息。
请注意,JWT的验证过程可以根据使用情况和需求而有所不同。你可以选择是否要验证特定声明、如何处理过期令牌等。验证过程的安全性和严格性取决于你的具体应用场景和安全要求。
1.6 JWT令牌刷新思路
JWT(JSON Web Token)令牌的刷新通常涉及以下思路:
- 令牌过期时间(exp): 在JWT中,可以设置一个过期时间(exp)声明,指示令牌的有效期。当令牌过期时,就需要进行刷新。
- 刷新令牌(Refresh Token): 除了JWT本身外,可以使用刷新令牌来实现令牌的刷新机制。刷新令牌是一个长期有效的令牌,用于获取新的JWT。刷新令牌通常具有自己的过期时间,较长,用于延长用户的会话。
- 刷新请求: 当JWT过期或即将过期时,客户端可以使用刷新令牌向身份验证服务器发送刷新请求。该请求通常包含刷新令牌和其他必要的信息。
- 身份验证服务器处理: 身份验证服务器接收到刷新请求后,会验证刷新令牌的有效性。如果刷新令牌有效,身份验证服务器会生成新的JWT,并将其返回给客户端。
- 更新本地存储: 客户端收到新的JWT后,可以更新本地存储中的令牌,以便在将来的请求中使用。
- 刷新令牌的限制: 要确保刷新令牌的安全性,应该采取一些措施,如定期更换刷新令牌、限制刷新令牌的使用次数等,以减小滥用的风险。
示例流程:
- 初始登录时,用户获得JWT和刷新令牌。
- 在JWT即将过期时,客户端使用刷新令牌向身份验证服务器请求新的JWT。
- 身份验证服务器验证刷新令牌,如果有效,则生成新的JWT并返回给客户端。
- 客户端使用新的JWT更新本地存储的令牌。
- 重复上述过程,使用户的身份保持有效。
需要注意的是,刷新令牌的使用需要谨慎,要确保它的安全性,避免在不安全的环境中暴露。此外,刷新令牌的管理和使用涉及到具体的安全策略和实现细节,需要根据具体的应用场景进行调整。
二、Jwt工具类
2.1 Jwt工具类是什么
在 Java 中,你可以使用第三方库来处理 JWT(JSON Web Tokens)。其中,"Java JWT" (jjwt) 是一个常用的库,用于创建、解析和验证 JWT 令牌。
2.2 Jwt工具类的使用
JwtUtils:
package com.zking.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("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,并重新设置签发时间(为当前时间)和失效时间 * * @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(); } }
2.2.1 生成Jwt
@Test public void test1() {// 生成JWT //JWT Token=Header.Payload.Signature //头部.载荷.签名 //Payload=标准声明+私有声明+公有声明 //定义私有声明 Map<String, Object> claims = new HashMap<String, Object>(); claims.put("username", "zss"); claims.put("age", 18); //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。在该方法中,定义了私有声明(claims),并设置了一些键值对,例如用户名和年龄。然后调用了JwtUtils.createJwt()
方法生成JWT,并打印输出。
复制此段生成的jwt,方便下一个方法进行解析如下:
2.2.2 解析Jwt
@Test public void test2() {// 解析oldJwt //io.jsonwebtoken.ExpiredJwtException:JWT过期异常 //io.jsonwebtoken.SignatureException:签名异常 //String oldJwt="eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTA3MTg2NzcsImlhdCI6MTU5MDcxNjg3NywiYWdlIjoxOCwianRpIjoiNDFmZjFiZGFkYzkxNDA3OGE4ZGUyNGRkZDEwYjU4N2IiLCJ1c2VybmFtZSI6InpzcyJ9.DdPvioX6kuhV6lEfD9QAN2eQSk_mO3dYkmDmTQsqa78"; //eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjE3MjcsImlhdCI6MTYzNTU1OTkyNywiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ9.dUR-9JUlyRdoYx-506SxXQ3gbHFCv0g5Zm8ZGzK1fzw String newJwt="eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY2MjM0Njg3MSwiaWF0IjoxNjYyMzQ1MDcxLCJhZ2UiOjE4LCJqdGkiOiI4YjllNzc3YzFlMDM0MjViYThmMDVjNTFlMTU3NDQ1MiIsInVzZXJuYW1lIjoienNzIn0.UWpJxPxwJ09PKxE2SY5ME41W1Kv3jP5bZGKK-oNUDuM"; 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会出现Jwt过期异常,如下:
然后我们把刚刚复制的Jwt串放入代码指定地方重新进行解析试试,优化后的代码如下:
@Test public void test2() {// 解析oldJwt //io.jsonwebtoken.ExpiredJwtException:JWT过期异常 //io.jsonwebtoken.SignatureException:签名异常 //String oldJwt="eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTA3MTg2NzcsImlhdCI6MTU5MDcxNjg3NywiYWdlIjoxOCwianRpIjoiNDFmZjFiZGFkYzkxNDA3OGE4ZGUyNGRkZDEwYjU4N2IiLCJ1c2VybmFtZSI6InpzcyJ9.DdPvioX6kuhV6lEfD9QAN2eQSk_mO3dYkmDmTQsqa78"; //eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjE3MjcsImlhdCI6MTYzNTU1OTkyNywiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ9.dUR-9JUlyRdoYx-506SxXQ3gbHFCv0g5Zm8ZGzK1fzw String newJwt="eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY5NzE4OTgxOSwiaWF0IjoxNjk3MTg4MDE5LCJhZ2UiOjE4LCJqdGkiOiJmOWM2MWE0YTRiZGE0OTY3YmY0NGM2ZjI3MmNiNzg2MSIsInVzZXJuYW1lIjoienNzIn0.AeIWdiKlOugiSB11PoA1s75u9kAq72RgX_idV-IkG_w"; 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)); }
然后重新运行Test2方法,结果如下:
2.2.3 复制JWT并延时30分钟
@Test public void test3() {// 复制jwt,并延时30分钟 String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY5NzE4OTgxOSwiaWF0IjoxNjk3MTg4MDE5LCJhZ2UiOjE4LCJqdGkiOiJmOWM2MWE0YTRiZGE0OTY3YmY0NGM2ZjI3MmNiNzg2MSIsInVzZXJuYW1lIjoienNzIn0.AeIWdiKlOugiSB11PoA1s75u9kAq72RgX_idV-IkG_w"; //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)); }
2.2.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)); }
2.2.5 模拟过期JWT的解析
@Test public void test5() {// 三秒后再解析上面过期时间只有三秒的令牌,因为过期则会报错io.jsonwebtoken.ExpiredJwtException //String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjMzODIsImlhdCI6MTYzNTU2MTU4MiwiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ1.F4pZFCjWP6wlq8v_udfhOkNCpErF5QlL7DXJdzXTHqE"; String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY5NzE3MzYwMywiaWF0IjoxNjk3MTczNjAwLCJqdGkiOiIwNWZiYzM1Nzc0MWU0ZGY4YTAyOTU0YTlmMGQ0N2I3NyIsInVzZXJuYW1lIjoienNzIn0.1hxptBpX9w_CfVKhJ19KDWoKzhtmZwbMWpnJkoGKzcE"; 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集成进spa项目
思路:
1.user登录方法,要放开用户信息生成jwt串保存到响应头中的代码
2.关闭jwtfilter中的off开关,代表开启jwt验证
3.crosfilter中要允许jwt使用请求头及响应头,换句话说web.xml要更换配置
具体操作如下:
web.xml:
<!--CrosFilter跨域过滤器--> <filter> <filter-name>corsFilter</filter-name> <filter-class>com.zking.ssm.util.CorsFilter</filter-class> </filter> <filter-mapping> <filter-name>corsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
然后将前端代码优化,如下:
state.js:
export default{ eduName:'死亡60HZ的支配', aname:'', bname:'', jwt:'' }
mutation.js:
export default { setEduName: (state, payload) => { //state指的就是state.js文件中导出的对象 //payload就是vue文件传递过来的参数 // A.Vue name {a_name:this.name} // B.Vue name {b_name:this.name} // state.aname = payload.a_name; // state.bname = payload.b_name; state.eduName = payload.eduName; }, setJwt: (state, payload) => { state.jwt = payload.jwt; } }
gettter.js:
export default { getEduName: (state) => { return state.eduname; }, getJwt: (state) => { return state.jwt; } }
最后在Api目录下找到http.js,把超时时间改成30s,如下:
// axios默认配置 axios.defaults.timeout = 30000; // 超时时间
在请求跟相应拦截器的代码中加入判断,如下:
// 请求拦截器 axios.interceptors.request.use(function(config) { let jwt =window.vm.$store.getters.getJwt; if(jwt){ config.headers['jwt'] = jwt; } return config; }, function(error) { return Promise.reject(error); }); // 响应拦截器 axios.interceptors.response.use(function(response) { let jwt = response.headers['jwt']; if(jwt){ //要将响应头中的jwt串放入到state.js中 window.vm.$store.commit('setJwt',{ jwt:jwt }); } return response; }, function(error) { return Promise.reject(error); });
完整页面如下,http.js:
/** * vue项目对axios的全局配置 */ import axios from 'axios' import qs from 'qs' import Vue from 'vue'; //引入action模块,并添加至axios的类属性urls上 import action from '@/api/action' // import { compile } from 'vue/types/umd'; axios.urls = action // axios默认配置 axios.defaults.timeout = 30000; // 超时时间 // axios.defaults.baseURL = 'http://localhost:8080/j2ee15'; // 默认地址 axios.defaults.baseURL = action.SERVER; //整理数据 // 只适用于 POST,PUT,PATCH,transformRequest` 允许在向服务器发送前,修改请求数据 axios.defaults.transformRequest = function(data) { data = qs.stringify(data); return data; }; // 请求拦截器 axios.interceptors.request.use(function(config) { let jwt =window.vm.$store.getters.getJwt; if(jwt){ config.headers['jwt'] = jwt; } return config; }, function(error) { return Promise.reject(error); }); // 响应拦截器 axios.interceptors.response.use(function(response) { let jwt = response.headers['jwt']; if(jwt){ //要将响应头中的jwt串放入到state.js中 window.vm.$store.commit('setJwt',{ jwt:jwt }); } return response; }, function(error) { return Promise.reject(error); }); // // 路由请求拦截 // // http request 拦截器 // axios.interceptors.request.use( // config => { // //config.data = JSON.stringify(config.data); // //config.headers['Content-Type'] = 'application/json;charset=UTF-8'; // //config.headers['Token'] = 'abcxyz'; // //判断是否存在ticket,如果存在的话,则每个http header都加上ticket // // if (cookie.get("token")) { // // //用户每次操作,都将cookie设置成2小时 // // cookie.set("token", cookie.get("token"), 1 / 12) // // cookie.set("name", cookie.get("name"), 1 / 12) // // config.headers.token = cookie.get("token"); // // config.headers.name = cookie.get("name"); // // } // return config; // }, // error => { // return Promise.reject(error.response); // }); // // 路由响应拦截 // // http response 拦截器 // axios.interceptors.response.use( // response => { // if (response.data.resultCode == "404") { // console.log("response.data.resultCode是404") // // 返回 错误代码-1 清除ticket信息并跳转到登录页面 // // cookie.del("ticket") // // window.location.href='http://login.com' // return // } else { // return response; // } // }, // error => { // return Promise.reject(error.response) // 返回接口返回的错误信息 // }); export default axios;
main.js:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' //开发环境下才会引入mockjs // process.env.MOCK && require('@/mock') // 新添加1 import ElementUI from 'element-ui' // 新添加2,避免后期打包样式不同,要放在import App from './App';之前 import 'element-ui/lib/theme-chalk/index.css' import App from './App' import router from './router' import store from './store' import axios from '@/api/http' import VueAxios from 'vue-axios' Vue.use(VueAxios,axios) // 新添加3 Vue.use(ElementUI) Vue.config.productionTip = false /* eslint-disable no-new */ window.vm = new Vue({ el: '#app', router, store, data(){ return{ Bus:new Vue() } }, components: { App }, template: '<App/>' })
展示结果如下:
最后Jwt简介+工具类应用+Jwt集成spa项目就到这里,祝大家在敲代码的路上一路通畅!