目录
前言
一、JWT简介
1. 什么是JWT
2. JWT的工作原理
3. JWT的组成
4. 使用JWT的好处
二、JWT工具类
JwtFilter
JwtUtils
工具类的作用
工具类的测试
测试代码
test01()测试结果
test02()测试结果
test03()测试结果
test04测试结果
test05测试结果
三、JWT集成SPA项目
后端
CorsFilter.java
CorsFilter2.java
前端
编写
测试
总结
编辑
前言
上一期与大家分享了VueX在Vue中的用途及一些用法,其中包含了VueX简介、使用VueX的准备工作、VueX获取值及设置值以及异步请求和同步请求。这期博客给大家带来有关JWT的知识分享,请各位老铁认真仔细看完哦。
一、JWT简介
1. 什么是JWT
JWT是JSON Web Token的缩写,是一种用于在网络应用间传递声明的开放标准。它使用JSON(JavaScript对象表示法)进行信息的安全传输,通常用于身份验证和授权。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
头部包含了描述JWT的元数据信息,如令牌的类型(JWT)和所用的签名算法(例如HMAC SHA256或RSA)。载荷是JWT的核心部分,包含了一些声明(claim),用于描述要传递的一些数据信息,如用户的ID、角色等。签名是通过对头部和载荷进行加密生成的,用于验证JWT的真实性和完整性。
JWT的优点在于它是无状态的,服务器不需要存储会话信息,只需根据JWT的签名进行验证即可。此外,JWT还可以设置过期时间和其他自定义的声明信息,提供了灵活性和安全性。
在使用JWT时,客户端通常在请求中将JWT放置在HTTP头部的Authorization字段中,服务器对JWT进行验证,验证通过后可以获取其中的信息来进行后续的操作。
2. JWT的工作原理
以下是JWT的工作原理(模拟展示):
- 用户通过用户名和密码进行身份验证。
- 服务器验证用户的凭据,如果验证成功,生成一个加密的JWT。
- 服务器将该JWT返回给客户端。
- 客户端将收到的JWT保存在本地,通常存储在浏览器的localStorage或cookie中。
- 客户端在后续的请求中,将JWT放置在HTTP头部的Authorization字段中,作为身份验证的凭据。
- 服务器在每次接收到请求时,通过验证JWT的签名来验证其真实性和完整性,并提取其中的信息进行权限验证或其他操作。
- 如果JWT通过验证且包含了有效的信息,服务器会对该请求进行处理,并返回响应结果。
- 客户端处理服务器返回的响应结果。
总之,客户端通过身份验证获取到JWT,将JWT作为凭据发送给服务器,服务器验证JWT的签名,提取其中的信息进行操作,完成后返回响应给客户端。JWT的使用避免了服务器存储会话信息的需求,提高了扩展性和灵活性,并提供了一种安全的身份验证和授权机制。
3. JWT的组成
JWT由三部分组成,它们分别是头部(Header)、载荷(Payload)和签名(Signature)。
头部(Header):头部是一个包含在JWT的第一个部分,它通常由两部分组成:令牌的类型(这里是JWT)和所使用的签名算法类型(例如HMAC SHA256或RSA)。头部使用Base64编码后与其他部分一起组成JWT的第一段。
载荷(Payload):载荷是JWT的第二个部分,也被称为声明(Claims)。它包含了一些有关用户或其他实体的信息,如用户的ID、角色、权限等。载荷可以包含标准声明(例如iss(发行者)、exp(过期时间)、sub(主题)等),也可以包含自定义的声明。载荷同样使用Base64编码后与其他部分一起组成JWT的第二段。
签名(Signature):签名是JWT的第三个部分,用于验证JWT的真实性和完整性。签名使用头部和载荷中的数据经过一种算法(如HMAC SHA256或RSA)进行加密生成。服务器在验证JWT时,会对头部、载荷和签名进行解码和验证,确保JWT未被篡改过。签名部分通常由服务器使用密钥进行生成,因此只有服务器才能生成有效的签名。
JWT的组成可以表示为:Base64编码的头部、Base64编码的载荷和服务器生成的签名。这三部分通过句点(.)进行连接,形成一个完整的JWT。例如:xxxxx.yyyyy.zzzzz。
4. 使用JWT的好处
使用JWT(JSON Web Token)有以下几个主要原因:
简单且轻量:JWT是一种简单且轻量的身份验证和授权机制。它使用JSON作为载荷,易于阅读和理解,并且可以轻松地在客户端和服务器之间传递。
无状态和可拓展性:JWT是无状态的,服务器不需要保存会话信息或用户状态。每个请求都包含JWT,服务器只需验证其签名,无需查询数据库或缓存。这使得服务器更容易扩展,而不必担心存储会话数据的负担。
安全性:JWT使用的签名机制确保了其真实性和完整性。服务器在验证JWT时,会比较签名是否有效,以确保JWT是否被篡改。此外,JWT还可以使用加密算法对载荷进行加密,以保护敏感信息的安全传输。
跨域支持:JWT可以在跨域环境下使用,因为它可以在HTTP头部或URL参数中进行传递。
可扩展性:JWT具有灵活性和可扩展性。除了标准的声明(Claims)之外,JWT还可以包含自定义的声明,可以根据实际需求定义和使用额外的数据。
前后端分离(去中心化):JWT适用于前后端分离的架构。前端在登录认证成功后,将JWT保存在客户端,每次请求时通过JWT进行身份验证,解决了传统基于会话的身份验证的问题。
总而言之,使用JWT可以提供简单、无状态、安全和可拓展的身份验证和授权机制,适用于现代化的应用程序架构。它简化了服务器的负载,提高了系统的性能和可扩展性,并提供了一种安全可靠的方式来进行身份验证和授权。
二、JWT工具类
JwtFilter
package com.yx.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 Administrator
*
*/
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);
// }
}
JwtUtils
package com.yx.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();
}
}
工具类的作用
JWT生成:JWT工具类可以提供简单的接口方法,用于生成JWT。它可以接收一些必要的信息(如用户ID、角色、过期时间等),并使用指定的签名算法对头部和载荷进行编码和签名,最终生成一个完整的JWT。
JWT解析:JWT工具类可以提供解析方法,用于解析JWT并提取其中的信息。它可以对JWT进行解码,并验证签名的真实性和完整性,提取出头部和载荷中的数据,如用户ID、角色、过期时间等。
JWT验证:JWT工具类可以提供验证方法,用于验证JWT的有效性和权限。它可以验证JWT的签名是否正确,以及JWT中所包含的用户角色和权限是否满足要求。这样可以确保只有具有有效JWT且具备相应权限的用户才能访问受保护的资源或执行敏感操作。
刷新令牌:JWT工具类可以提供刷新令牌的功能,允许用户在过期时间之前更新JWT。当用户的JWT即将过期时,可以使用刷新令牌来获取新的有效JWT,避免重新进行身份验证。
自定义配置:JWT工具类通常提供了一些可配置的选项,可以根据项目需求进行自定义设置,如选择不同的签名算法、调整过期时间、指定密钥等。
工具类的测试
测试代码
package com.yx.ssm.service.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.zking.ssm.jwt.JwtUtils;
import io.jsonwebtoken.Claims;
import org.junit.*;
public class JwtDemo {
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", "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));
}
@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));
}
@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));
}
@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));
}
@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));
}
}
test01()测试结果
说明该工具类可以生成字符串,可以获取信息。
test02()测试结果
因为test02方法用的是很久之前的字符串测试所以会失效,无法访问到信息。如果我们测试test01方法,将test01方法生成的字符串保存下来覆盖test02的字符串,我们就可以访问对应的信息。(如下图所示)
test03()测试结果
方法三是为了延长字符串的时间,结果图下
该方法的字符串使用的和方法二的一致,与上面的比较发现方法三可以将我们的字符串延长了30分钟,当然我们可以根据自身的需求对是时间的延长进行自定义设置。
test04测试结果
该方法使用用于测试根据信息查看字符串有效时间
test05测试结果
三秒后再解析上面过期时间只有三秒的令牌,因为过期则会报错io.jsonwebtoken.ExpiredJwtException
三、JWT集成SPA项目
后端
CorsFilter.java
package com.zking.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() {
}
}
CorsFilter2.java
package com.zking.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.HttpServletResponse;
/**
* 配置tomcat允许跨域访问
*
* @author Administrator
*
*/
public class CorsFilter2 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;
// Access-Control-Allow-Origin就是我们需要设置的域名
// Access-Control-Allow-Headers跨域允许包含的头。
// Access-Control-Allow-Methods是允许的请求方式
httpResponse.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
httpResponse.setHeader("Access-Control-Allow-Headers", "responseType,Origin, X-Requested-With, Content-Type, Accept");
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
//允许客户端处理一个新的响应头jwt
//httpResponse.setHeader("Access-Control-Expose-Headers", "jwt,Content-Disposition");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
二者区别在于CorsFilter.java多例以下代码
//允许客户端发一个新的请求头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");不添加改代码无法生成请求头和相应头,还要将web.xml中的CorsFilter2改为CorsFilter。
步骤:
(1)在登陆方法中要放开用户生成JWT信息保存到响应投中的代码
(2)打开JWT验证
(3)在crosfilter中允许使用请求头和响应头
前端
编写
在store文件下的actions.js中定义一个jwt变量接收jwt
在store文件下的mutations.js和getters.js中分别定义set和get方法
在 main.js中给vue示例起别名方便后续使用
在http.js中的编写请求头代码和响应头代码
测试
总结
JWT实现执行如上图
今天的分享到此结束,感谢大家观看,给个三连加关注支持一下。