JWT 简介
-
JWT:Json Web Token
-
优点:可生成安全性较高的 token 且可以完成时效性的检验(登陆过期检查)
-
JWT 结构:(由官网获取)
JWT 生成 token
添加依赖:
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成 token 示例代码:
//使用jwt生成token进行加密
JwtBuilder builder = Jwts.builder();
//此map可以存储用户角色权限信息
Map<String, Object> map = new HashMap<>();
map.put("k1", "v1");
//链式调用,设置相关加密信息
String token = builder.setSubject(name) //设置主题,也就是设置token中携带的数据
.setIssuedAt(new Date()) //设置token生成时间
.setId(users.get(0).getUserId() + "") //设置token的id(此处用的用户id)
.setClaims(map) //map中可存放用户角色权限信息
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //设置token过期时间为1天后
.signWith(SignatureAlgorithm.HS256, "luis333") //设置加密方式和加密密码
.compact();
JWT 解析 token
//获取token并校验
if (token == null) {
return new ResultVo(ResStatus.NO, "请先登陆!", null);
} else {
//获取jwt解析器
JwtParser parser = Jwts.parser();
parser.setSigningKey("luis333"); //之前加密时的加密密码,一致才能解析成功
try {
//如果token正确(密码正确,且在有效期内)则正常执行,否则抛出异常
Jws<Claims> claimsJws = parser.parseClaimsJws(token); //解析token
//获取解析的token中相关数据
Claims body = claimsJws.getBody(); //获取token中用户数据
String subject = body.getSubject(); //获取subject中数据
String v1 = body.get("k1", String.class); //获取claims中存储的map中数据
return new ResultVo(ResStatus.OK, "success", null);
} catch (Exception e) {
return new ResultVo(ResStatus.NO, "登陆过期,请重新登陆!", null);
}
}
拦截器校验 token
请求非常多,一个一个地进行校验不现实,所以需要统一校验 token,此时可以使用拦截器。
在拦截器的预处理方法中进行 token 统一校验,校验通过则放行请求,不通过则做出相关响应提示。
在拦截器统一校验 token 后,在控制器中做过校验的请求就不需要再次进行校验了,直接写业务逻辑即可。
拦截器类:
/**
* 拦截器
*/
@Component
public class CheckTokenInterceptor implements HandlerInterceptor {
/**
* 预处理方法,拦截请求,决定是否放行。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取token并校验
String token = request.getParameter("token");
System.out.println("preHandle ========> token = " + token);
if (token == null) {
//没有token
ResultVo resultVo = new ResultVo(ResStatus.NO, "请先登录!", null);
doResponse(response, resultVo);
} else {
//获取jwt解析器
JwtParser parser = Jwts.parser();
parser.setSigningKey("luis333"); //之前加密时的加密密码,一致才能解析成功
try {
//如果token正确(密码正确,且在有效期内)则正常执行,否则抛出异常
Jws<Claims> claimsJws = parser.parseClaimsJws(token); //解析token
return true;
} catch (ExpiredJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "token已过期!", null);
doResponse(response, resultVo);
} catch (UnsupportedJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "异常token!", null);
doResponse(response, resultVo);
} catch (Exception e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "请先登录!", null);
doResponse(response, resultVo);
}
}
return false;
}
/**
* 响应流程封装
*/
private void doResponse(HttpServletResponse response, ResultVo resultVo) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String s = new ObjectMapper().writeValueAsString(resultVo);
out.print(s);
out.flush();
out.close();
}
}
拦截器配置类:
/**
* 拦截器配置类(SpringBoot中配置拦截器)
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer { //注意此处需要实现WebMvcConfigurer接口
@Autowired
private CheckTokenInterceptor checkTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkTokenInterceptor) //添加拦截器
.addPathPatterns("/shopcart/**") //添加要拦截的请求
.addPathPatterns("/orders/**")
.excludePathPatterns("/user/**"); //放行的请求
}
}
常用请求头传递 token
前端但凡访问受限资源,都必须携带 token 发起请求;token 可以通过请求行(params)、请求头(headers)以及请求体(data)传递,但是习惯性使用 headers 传递!
在实际开发中,发送请求时,我们一般都是在请求头中来传递 token,通过自定义请求头数据,传递 token 到后端。
前端使用自定义请求头数据传递 token 示例:(注意请求头传递数据关键字 headers,带 s)
<script type="text/javascript">
var baseUrl = "http://localhost:8080";
var vm = new Vue({
el: "#container",
data: {
token: ""
},
//钩子函数,生命周期函数,直接定义!
created: function() {
//钩子函数,data数据初始化之后自动调用
console.log("========> created method execute !")
var token = getCookieValue("token");
this.token = token;
console.log("token = " + token);
axios({
url: baseUrl + "/shopcart/list",
method: "get",
headers: {token: this.token} //自定义请求头数据传递token
}).then(function(res) {
console.log(res.data);
});
}
});
</script>
注意:使用自定义请求头传递数据时,浏览器会自动发起一次预检请求(method="OPTIONS"
),来监测环境是否畅通,如果服务器正常响应,则后续请求才可正常发起。
所以,当使用拦截器统一校验 token ,并且使用自定义请求头传递 token 时,需要在拦截器中放行浏览器自动发起的预检请求,即放行 OPTIONS
请求方式的预检请求。
主要注意点:
拦截器代码示例:
/**
* 拦截器
*/
@Component
public class CheckTokenInterceptor implements HandlerInterceptor {
/**
* 预处理方法,拦截请求,决定是否放行。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//放行浏览器自动发起的预检请求(请求方式为OPTIONS)
String method = request.getMethod();
System.out.println("method ========> " + method);
if ("OPTIONS".equalsIgnoreCase(method)) {
return true;
}
//从请求头中获取token并校验
String token = request.getHeader("token");
System.out.println("preHandle ========> token = " + token);
if (token == null) {
//没有token
ResultVo resultVo = new ResultVo(ResStatus.NO, "请先登录!", null);
doResponse(response, resultVo);
} else {
//获取jwt解析器
JwtParser parser = Jwts.parser();
parser.setSigningKey("luis333"); //之前加密时的加密密码,一致才能解析成功
try {
//如果token正确(密码正确,且在有效期内)则正常执行,否则抛出异常
Jws<Claims> claimsJws = parser.parseClaimsJws(token); //解析token
return true;
} catch (ExpiredJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "token已过期!", null);
doResponse(response, resultVo);
} catch (UnsupportedJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "异常token!", null);
doResponse(response, resultVo);
} catch (Exception e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "请先登录!", null);
doResponse(response, resultVo);
}
}
return false;
}
/**
* 响应流程封装
*/
private void doResponse(HttpServletResponse response, ResultVo resultVo) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String s = new ObjectMapper().writeValueAsString(resultVo);
out.print(s);
out.flush();
out.close();
}
}