前言
在我们做项目时,每次浏览器向后端发送请求时,需要让后端知道这是哪个用户在操作的,它是基于token会话跟踪技术,这个功能的实现就是web会话跟踪技术
会话跟踪技术的实现
1.token的生成
会话跟踪技术中,在登录成功后,在后端生成一个token(字符串),可携带用户信息,加密
①JWT是生成token的方式
优点:简洁
可以携带用户信息
可以加密
不需要在服务器端存储,节省空间
②如何生成token?
❶导入jwt组件的jar
❷导入JWTUtil类
示例如下:
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;
import java.util.HashMap;
import java.util.Map;
public class JWTUtil {
/**
* jwt 生成 token
* @param id
* @param account * @return
*/
public static String token (Integer id, String account){
String token = "";
try {
//过期时间 为 1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() +60*60*24*1000);
//秘钥及加密算法,加盐
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");//生成类型
header.put("alg","HS256");//加密算法
//携带 id,账号信息,生成签名
token = JWT.create()
.withHeader(header)//头部
.withClaim("id",id)//用户id
.withClaim("account",account)//用户账号
.withExpiresAt(expireDate)//过期时间
.sign(algorithm);
}catch (Exception e){
e.printStackTrace(); return null;
}
return token;
}
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
//当传过来的 token 如果有问题,抛出异常
return false;
}
}
/**
* 获得 token 中 playload 部分数据,按需使用
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
}
}
2.token进行响应
①当token生成之后,将其放入到admin用户数据中并且响应给前端
示例如下:
//登录成功,生成token,token中携带用户信息
//在后端将token字符串添加到admin中,并且响应给前端
String token = JWTUtil.token(admin.getId(), admin.getAccount());
admin.setToken(token);
②在浏览器中将token字符串进行存储
//sessionStorage是浏览器提供的一个会话级别的存储空间,浏览器关闭后立刻消失
sessionStorage.setItem("token",resp.data.data.token);
③当重新进入一个组件时,token每次与后端交互,将数据放在请求头中, 然而每次发送都比较麻烦,因此可以在main.js中配置一个axios请求拦截器,这样就不用每次都发送,减少代码冗余
示例如下:
//axios 请求拦截
axios.interceptors.request.use(config =>{
//向请求头中添加一个自定义的请求头,只需要定义一次即可
config.headers.token = sessionStorage.getItem('token');
return config;
})
3.接收请求头中的数据对token进行解析验证
①当token传到后端时,每次都需要进行验证,比较麻烦,因此我们可以将验证写进一个过滤器中,因此再进入目标Servlet之前,经过过滤器,验证token
示例如下:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffyc.webBack.util.CommonResult;
import com.ffyc.webBack.util.JWTUtil;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req =(HttpServletRequest)servletRequest;
String token = req.getHeader("token");//获取请求头中的数据
boolean res = JWTUtil.verify(token);//验证token
if(res){
//token 没有问题,让请求继续向下执行,由目标的selvet进行响应
filterChain.doFilter(servletRequest,servletResponse);
}else{
//token验证失败
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setHeader("Content-Type", "text/html;charset=utf-8");//设置响应内容的编码
PrintWriter pt = response.getWriter();
CommonResult commonResult=new CommonResult(202,res, "验证token失败");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
pt.print(json);
}
}
}
<!--配置token过滤器-->
<filter>
<filter-name>TokenFilter</filter-name>
<filter-class>com.ffyc.webBack.filter.TokenFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TokenFilter</filter-name>
<url-pattern>/admin/*</url-pattern><!--因为token是在登录成功之后进行生成,因此在配置地址时,要跳过登录地址-->
</filter-mapping>
②当验证token成功后,进行解析
示例如下:
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Content-Type", "text/html;charset=utf-8");
PrintWriter pt = resp.getWriter();
String token = req.getHeader("token");//获取token
DecodedJWT tokenInfo = JWTUtil.getTokenInfo(token);//解析token
Integer id = tokenInfo.getClaim("id").asInt();//取出token的id
String account = tokenInfo.getClaim("account").asString();//取出token的account
System.out.println("测试请求"+id+":"+account);
CommonResult commonResult = new CommonResult(200, null, "测试成功");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
pt.print(json);
}
}
③将token验证及解析内容响应到前端(知道是哪个用户操作的)
test() {
//向后端携带token
this.$http.get("/admin/test").then((resp) => {
if (resp.data.code == 200) {
alert("测试成功");
}
})
}
④token有可能过期或者更改,那么每个测试或组件都需要写出错情况下的代码,因此我们可以在main.js中配置一个响应拦截器,如果出现token验证失败,则直接进行拦截,减少代码重复性
示例如下:
// 添加响应拦截器
axios.interceptors.response.use((resp) =>{
//正常响应拦截
if(resp.data.code==500){
ElementUI.Message({message:resp.data.message,type:"error"})
}
if(resp.data.code==202){
sessionStorage.clear();//token验证失败,认为登录失效,可以清空存储信息,让其重新登录
router.replace("/login");
}
return resp;
});
总结
- 以上工作做完之后,使用token(令牌)进行对用户信息的监测和验证,才可以说前后端交互方面的信息处理才比较安全,这样才会使用户操作的更加安全可靠,当然也是前后端交互时必不可少的事情