烟台建站程序,湛江专业网站制作,手机微信网页版,网站怎么做微信扫描登录网站目录
前言
一、JWT简介
1. 什么是JWT#xff1f;
编辑
2. JWT的工作原理
3.JWT如何工作的
4. JWT的优势
5. 在实际应用中使用JWT
6.传统Session和JWT认证的区别
6.1.session认证方式
6.2.JWT认证方式
7.基于Token的身份认证 与 基于服务器的身份认证
二、JWT的…目录
前言
一、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.前端编写 前言
互联网安全一直是用户和开发者们关注的焦点。本文介绍了一种名为JWTJSON Web Token的新型安全传输标准探讨了其工作原理、优势并展示了在实际应用中的强大功能。无论你是一个前端开发者、后端工程师还是安全专家这篇博客都将为你揭开JWT的神秘面纱并带来惊喜和启发。
一、JWT简介
1. 什么是JWT
JWTJSON Web Token是一种开放的标准RFC 7519用于在不同实体之间安全地传输、认证和验证数据。它使用 JSON 对象作为载荷Payload并使用数字签名或加密算法对其进行安全性保护。JWT通常由三个部分组成头部Header、载荷Payload和签名Signature。
官网地址:https://jwt.io/introduction 2. JWT的工作原理 头部Header头部包含了描述签名算法和加密算法等元数据的信息。它通常由两部分组成令牌类型typ和加密算法alg。例如{typ: JWT, alg: HS256}表示该令牌使用HS256算法进行签名。 载荷Payload载荷是真正存储数据的部分。它包含了一些声明Claim用于描述数据的一些属性和相关信息比如用户ID、角色、过期时间等。载荷可以被加密或签名但不能包含敏感信息。 签名Signature签名是对头部和载荷进行数字签名以保证数据的完整性和安全性。签名需要使用到密钥确保只有持有正确密钥的实体才能验证签名并且防止数据篡改。 上述原理图描述 应用程序或客户端向授权服务器请求授权。授予授权后授权服务器将向应用程序返回访问令牌。应用程序使用访问令牌访问受保护的资源(如 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,以便下次请求时发送给我们的应用这样应用就能识别请求来自哪个用户。 暴露的问题 用户经过应用认证后应用都要在服务端做一次记录以方便用户下次请求的鉴别通常而言session都是保存在内存中而随着认证用户的增多服务端的开销会明显增大用户认证后服务端做认证记录如果认证的记录被保存在内存中的话用户下次请求还必须要请求在这台服务器上这样才能拿到授权的资源。在分布式的应用上限制了负载均衡器的能力。以此限制了应用的扩展能力session是基于cookie来进行用户识别cookie如果被截获用户很容易受到CSRF跨站伪造请求攻击)攻击在前后端分离系统中应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session每次携带sessionid到服务器服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中给服务器增加负担。还有就是sessionid就是一个特征值表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。 6.2.JWT认证方式 认证流程 前端通过Web表单将自己的用户名和密码发送到后端的接口。该过程一般是HTTP的POST请求。建议的方式是通过SSL加密的传输(https协议)从而避免敏感信息被嗅探。后端核对用户名和密码成功后将用户的id等其他信息作为JWT Payload(负载)将其与头部分别进行Base64编码拼接后签名形成一个JWT(Token)。后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage浏览器本地缓存或sessionStoragesession缓存上退出登录时前端删除保存的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的状态是存储在客户端。 7.3. 基于Token的身份认证是如何工作的 基于Token的身份认证是无状态的服务器或者Session中不会存储任何用户信息。 没有会话信息意味着应用程序可以根据需要扩展和添加更多的机器而不必担心用户登录的位置。 虽然这一实现可能会有所不同但其主要流程如下 -用户携带用户名和密码请求访问 -服务器校验用户凭据 -应用提供一个token给客户端 -客户端存储token并且在随后的每一次请求中都带着它 -服务器校验token并返回数据 注意 -每一次请求都需要token -Token应该放在请求header中 -我们还需要将服务器设置为接受来自所有域的请求用Access-Control-Allow-Origin: * 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由以点(.)分隔的三个部分组成它们是: HeaderPayloadSignature 类似于xxxx.xxxx.xxxx格式,真实情况如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c并且你可以通过官网JSON Web Tokens - jwt.io解析出三部分表示的信息( 可使用 JWT.io Debugger 来解码、验证和生成 JWT ): (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的第二部分eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQregistered 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_TTLWEBAPP应用中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密匙生成的加密keystatic {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(MapString, Object claims, long ttlMillis) {// 生成JWT的时间即签发时间 2021-10-30 10:02:00 - 30 10:32:00long nowMillis System.currentTimeMillis();//链式语法// 下面就是在为payload添加各种标准声明和私有声明了// 这里其实就是new一个JwtBuilder设置jwt的bodyJwtBuilder 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获取所有的声明私有和标准//oldClaims claims parseJwt(jwt);// 生成JWT的时间即签发时间long nowMillis System.currentTimeMillis();// 下面就是在为payload添加各种标准声明和私有声明了// 这里其实就是new一个JwtBuilder设置jwt的bodyJwtBuilder 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工具类中的方法注释 public static final long JWT_WEB_TTL 30 * 60 * 1000;定义了一个常量表示Web应用中JWT的有效时间单位为毫秒默认值为30分钟。 public static final String JWT_HEADER_KEY jwt;定义了一个常量表示在header中保存JWT的键名默认为jwt。 private static final SignatureAlgorithm SIGNATURE_ALGORITHM SignatureAlgorithm.HS256;定义了一个静态常量表示签名算法这里使用的是HS256。 private static final String JWT_SECRET f356cdce935c42328ad2001d7e9552a3;定义了一个静态常量表示JWT的密钥。 static {...}这是一个静态代码块用于初始化JWT的密钥。首先将JWT的密钥从Base64格式解码为字节数组然后使用这个字节数组创建一个SecretKey对象。 public static Claims parseJwt(String jwt) {...}这是一个静态方法用于解析JWT返回一个Claims对象其中包含了JWT的所有声明。 public static String createJwt(MapString, Object claims, long ttlMillis) {...}这是一个静态方法用于创建JWT令牌。首先根据传入的claims和ttlMillis计算出JWT的签发时间和过期时间然后使用Jwts的builder模式构建JWT并设置签名算法和密钥最后返回生成的JWT字符串。 public static String copyJwt(String jwt, Long ttlMillis) {...}这是一个静态方法用于复制JWT。首先调用parseJwt方法解析传入的JWT获取其所有的声明然后使用这些声明和传入的ttlMillis创建一个新的JWT并返回。 2.JWT的生成与解析
通过Java代码实现JWT的生成( 使用的是JJWT框架 )
先导入JJWT的依赖(JJWT是JWT的框架) dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version/dependency
测试代码如下:
private SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss.SSS);Testpublic void test1() {// 生成JWT//JWT TokenHeader.Payload.Signature//头部.载荷.签名//Payload标准声明私有声明公有声明//定义私有声明MapString, Object claims new HashMapString, Object();claims.put(username, ctb);claims.put(age, 23);//TTL:Time To LiveString jwt JwtUtils.createJwt(claims, JwtUtils.JWT_WEB_TTL);System.out.println(jwt);//获取Payload包含标准和私有声明Claims parseJwt JwtUtils.parseJwt(jwt);for (Map.EntryString, 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));}
运行结果 通过官网进行解码 通过Java代码实现JWT的解码( 使用的是JJWT框架 )
测试代码如下:
Testpublic void test2() {// 解析oldJwt//io.jsonwebtoken.ExpiredJwtExceptionJWT过期异常//io.jsonwebtoken.SignatureException签名异常//eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjE3MjcsImlhdCI6MTYzNTU1OTkyNywiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ9.dUR-9JUlyRdoYx-506SxXQ3gbHFCv0g5Zm8ZGzK1fzwString newJwteyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjdGIiLCJleHAiOjE2OTcyMDYzNDYsImlhdCI6MTY5NzIwNDU0NiwiYWdlIjoyMywianRpIjoiYmQ2OTNiYWIxZDAxNDMwMWExMmNjOGMyNDJkNDdmOGEiLCJ1c2VybmFtZSI6ImN0YiJ9.lWtz13pyHJUYWd2OrSLE-MGYmHqzvACnAtIJOUFS1UM;
// String oldJwt eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzU1NjE3MjcsImlhdCI6MTYzNTU1OTkyNywiYWdlIjoxOCwianRpIjoiN2RlYmIzM2JiZTg3NDBmODgzNDI5Njk0ZWE4NzcyMTgiLCJ1c2VybmFtZSI6InpzcyJ9.dUR-9JUlyRdoYx-506SxXQ3gbHFCv0g5Zm8ZGzK1fzw;Claims parseJwt JwtUtils.parseJwt(newJwt);for (Map.EntryString, 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));}
运行结果 3.token刷新并延长默认有效时间
测试代码如下
Testpublic 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.EntryString, 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的相关信息。
运行结果 4.测试JWT的有效时间
测试代码如下
Testpublic void test4() {// 测试JWT的有效时间MapString, Object claims new HashMapString, 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秒。 运行结果 5.模拟JWT令牌过期
测试代码如下
Testpublic 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异常。
运行结果 四、案例讲解
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 {Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic 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);//允许客户端发一个新的请求头jwthttpResponse.setHeader(Access-Control-Allow-Headers,responseType,Origin,X-Requested-With, Content-Type, Accept, jwt);//允许客户端处理一个新的响应头jwthttpResponse.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);}Overridepublic void destroy() {}
}
在web.xml进行配置过滤器 !--CrosFilter跨域过滤器--filterfilter-namecorsFilter/filter-namefilter-classcom.ctb.ssm.util.CorsFilter/filter-class/filterfilter-mappingfilter-namecorsFilter/filter-nameurl-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令牌验证功能Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic void destroy() {}Overridepublic 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.actionString 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对象 - ModulesBoolean flagfalse;if (null claims) {// resp.setCharacterEncoding(UTF-8);resp.sendError(403, JWT令牌已过期或已失效);return;} else {//1.获取已经解析后的payload私有声明//2.从私有声明中当前用户所对应的权限集合ListString或者ListModule//3.循环权限(Module[id,url])// OK,放行请求 chain.doFilter(request, response);// NO,发送错误信息的JSON// ObjectMapper mappernew 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?usernamezspassword123;// Matcher matcher PATTERN.matcher(path);// boolean b matcher.matches();// System.out.println(b);// }}注private boolean OFF false;我们在开发过程中可以通过这个属性来决定我们要不要使用JWT如果我们开发过程都需要JWT的话测试是非常麻烦的。
在web.xml也需配置
!--JwtFilter--filterfilter-namejwtFilter/filter-namefilter-classcom.ctb.ssm.jwt.JwtFilter/filter-class/filterfilter-mappingfilter-namejwtFilter/filter-nameurl-pattern/*/url-pattern/filter-mapping
UserController
在controller层编写保存JWT的方法并回显给前端
/*** 登录* param userVo* param response* return*/RequestMapping(/userLogin)ResponseBodypublic JsonResponseBody? userLogin(UserVo userVo, HttpServletResponse response){if(userVo.getUsername().equals(admin)userVo.getPassword().equals(123)){//私有要求claimMapString,Object jsonnew HashMapString,Object();json.put(username, userVo.getUsername());//生成JWT并设置到response响应头中String jwtJwtUtils.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生效了在第二次请求中没有带JWT是过不了后端请求的若是我们没有借助JWT认证那我们就可以一直发请求这是不安全的