创作灵感
在现在的开发中,token已经成为了校验用户的非常重要的一部分。但在使用token时,有些时候可能会遇到一些问题,故此,我写下这篇文章以供大家参考。
后端添加token
在后端使用token时,第一步便是添加相应的依赖包。由于我使用了maven进行包管理,其导入的坐标如下:
<!--token相关的包-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
在导入jar包以后,下一步便是编写token的生成与校验,这需要单独书写一个工具类,我书写的为token类,如下:
package com.nccolleage.until;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
/**
* 时间:2023/12/7
* 作者:杨志豪
* 说明:用于token的生成与验证
*/
public class Token {
//token密钥
private static final String secret = "hand2020";
//过期时间(一周)
private static final long expiration = 7*24*3600*1000;
/**
* 生成用户token,设置token超时时间
*/
public static String createToken(String name) {
//过期时间
Date expireDate = new Date(System.currentTimeMillis() + expiration);
String token = JWT.create()
// 添加头部
.withIssuer("auth0")
//可以将基本信息放到claims中
.withClaim("userName", name)
//超时设置,设置过期的日期
.withExpiresAt(expireDate)
//SECRET加密
.sign(Algorithm.HMAC256(secret));
return token;
}
/**
* 校验token并解析token
*/
public static boolean verifyToken(String token) {
DecodedJWT jwt;
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).withIssuer("auth0").build();
jwt = verifier.verify(token);
if (jwt.getExpiresAt().before(new Date())) {
System.out.println("token过期");
return false;
}
return true;
} catch (Exception e) {
//解码异常则抛出异常
System.out.println("token解析异常:" + e.getMessage());
return false;
}
}
}
在上述代码之中,主要有两个方法,一个用于生成token,一个用于校验token。需要注意的是,生成token中所传入的name应该是唯一的,例如,每个用户的账号都不同,就可以用账号进行token的生成。其他的说明代码中均有说明。
后台添加拦截器以处理token
在后台新增token以后,我们希望用户的某些请求只有在登入以后并携带token才能够访问到。这就需要请求拦截器,拦截请求并获取其请求头内部的token。关于token的放置,大部分都是放置在请求头内,因为我们很多不同的请求都需要token,所以放在请求头内是比较合适的。
以下为拦截器代码:
package com.nccolleage.until;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 拦截器
* 时间:2023/12/8
*/
public class MyInterceptor implements HandlerInterceptor {
// 在请求接口方法之前进入,如果放回true就放行,进入下一个拦截器或者控制器,如果返回false就不继续往下走
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {//对options请求进行处理
return true;
}
String account=request.getParameter("account");
String url= String.valueOf(request.getRequestURL());
if(url.equals(StaticClass.baseUrl+"user")){//用户注册放行
return true;
}
if(url.equals(StaticClass.baseUrl+"commission")){//获取悬赏放行
return true;
}
if(url.equals(StaticClass.baseUrl+"ask")){//获取问答放行
return true;
}
if(url.equals(StaticClass.baseUrl+"article/getArticles")){//获取八卦放行
return true;
}
if(url.equals(StaticClass.baseUrl+"reply/getRepliesByAskId")){//获取问题的评论放行
return true;
}
if(url.contains("images")||url.contains("chatPictures")){//放行用户获取照片
return true;
}
//将空置为空字符串
if(account==null){
account="";
}
if(account.isEmpty()){
String token=request.getHeader("Token");
if(token==null){
token="";
}
if(!Token.verifyToken(token)){//token校验失败,提醒用户重新登入
response.setStatus(401);
}
return true;
//return Token.verifyToken(token);
} else {
return true;
}
}
//调用前提:preHandle返回true
//调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作
//执行顺序:链式Interceptor情况下,Interceptor按照声明的顺序倒着执行。
//备注:postHandle虽然post打头,但post、get方法都能处理
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
}
//调用前提:preHandle返回true
//调用时间:DispatcherServlet进行视图的渲染之后
//多用于清理资源
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
}
}
在这段代码里面,如果你有部分资源需要不携带token也能进行访问,则对其进行放行便是了。这里我需要着重提醒的是:
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {//对options请求进行处理
return true;
}
有些人可能对这段代码不是非常了解。注释内说的是对options请求进行处理(其实就是对其进行放行)。这里我需要对其进行更详细的讲解。
详解options请求
首先通俗的说,options请求是一个预检请求。
什么又是预检请求呢?这么说吧,当我们在发送一个比较复杂的请求时,浏览器会觉得,这个请求这么复杂,如果直接发送,会不会不成功并且消耗大量时间?所有会先发送一次预请求,如果通过了才会按照我们的配置进行真正的请求。
而预请求出现的条件又是什么呢?一般来说其有三个条件,满足其中两个时便会出现预请求。这里我简述一下我目前所满足的两个条件:
1:修改了请求头。毫无疑问,我们将token放置在了请求头内部,就已经满足了这个条件。
2:跨域请求。在现在前后端分离的大背景下跨域请求也已经不是什么奇怪的事情了。
那么现在出现的问题就是,按照我们的配置,预请求是不可避免的。而预请求内部是不会携带token的。这一点也非常好理解,本来预请求就是为了检查一下简单的请求是否能通过,而简单的请求是不会修改请求头的。然而,也正是因为这一点,预请求不携带token,导致后台将请求拦截了下来,浏览器发现预请求都不能成功,就会认为比预请求的真正的请求更加不能成功了。其就不会发送真正的请求了。
这是不是有一点点绕?简单来说,就是浏览器发现你的请求是一个复杂请求,为了避免网络拥堵,先进行一次简单的测试(浏览器自主行为,不会携带token)。测试发现不通(因为自主测试并未携带token)。不通后就认为你的请求是不可能通过的,干脆就不发送了。下面谈谈如何解决这一现象。
解决options预请求不通过
解决的方式其实很简单,那就是后台在发现有options请求时,直接对其放行,放行后,浏览器就认为这次复杂请求是有可能成功的,就会进行真正的请求。
这就是为什么会出现上述的红色代码。
部分实例图
为了方便大家更好的理解,我向大家提供以下部分实例图片
第一次的OPTIONS请求(下方请求头未携带token)
第二次真正的请求(下方请求头携带了token)
制作不易,还望大家点赞鼓励一下。