转载地址:https://blog.csdn.net/kkkun_joe/article/details/81878231
以上博主讲的更清除些,此博客是为了自己加深记忆。
对于前后端分离的项目来说session来判断是否登陆实现比较困难,token是比较好的方式。
大概流程:
1.用户登陆,若成功则后台生成一个token,并把此token返回给客户端浏览器
2.客户端接收到token后,每次请求都要把此token放到header中发给后段
3.后段使用拦截器判断token的正确性和实效性。
以下是具体代码:
Token工具类:
package com.sign;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class TokenSign {
/**
* 过期时间60分钟
*/
private static final long EXPIRE_TIME=60 * 60 *1000;
/**
* 私钥,使用它生成token,最好进行下加密
*/
private static final String TOKEN_SECRET="Token";
/**
* 产生token
* @param useName
* @param userId
* @return
*/
public static String sign(String useName,String userId){
try{
//设置15分钟失效
Date date=new Date(System.currentTimeMillis()+EXPIRE_TIME);
//私钥及加密算法
Algorithm algorithm=Algorithm.HMAC256(TOKEN_SECRET);
//设置头部信息
Map header=new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//附带username和userid信息,存储到token中,生成签名
return JWT.create()
.withHeader(header)
//存储自己想要留存给客户端浏览器的内容
.withClaim("userName",useName)
.withClaim("userId",userId)
.withExpiresAt(date)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* token校验是否正确
* @param token
* @return
*/
public static boolean verify(String token){
try {
Algorithm algorithm=Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier =JWT.require(algorithm).build();
//此方法若token验证失败会抛错的,所以直接return true没问题
DecodedJWT decodedJWT =verifier.verify(token);
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
/**
* 获取token中信息 userName
* @param token
* @return
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("userName").asString();
} catch (JWTDecodeException e) {
e.getStackTrace();
}
return null;
}
/**
* 获取token中信息 userId
* @param token
* @return
*/
public static String getUserId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("userId").asString();
} catch (JWTDecodeException e) {
e.getStackTrace();
}
return null;
}
}
拦截器:
package com.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.constant.TokenConstant;
import com.sign.TokenSign;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class LoginInterceptor implements HandlerInterceptor {
// 在请求处理之前调用,只有返回true才会执行要执行的请求
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
httpServletResponse.setCharacterEncoding("UTF-8");
String token=httpServletRequest.getHeader("accessToken");
if (null==token){
Map map=new HashMap<>();
map.put("data","token is null");
map.put("code","401");
httpServletResponse.getWriter().write(JSONObject.toJSONString(map));
return false;
}else {
boolean result= TokenSign.verify(token);
if (result){
//更新存储的token信息
TokenConstant.updateTokenMap(token);
return true;
}
Map map=new HashMap<>();
map.put("data","token is null");
map.put("code","401");
httpServletResponse.getWriter().write(JSONObject.toJSONString(map));
return false;
}
}
// 试图渲染之后执行
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
// 在请求处理之后,视图渲染之前执行
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
package com.constant;
import java.util.HashMap;
import java.util.Map;
public class TokenConstant {
private static Map map=new HashMap();
public static String getToken(){
return map.get("token");
}
public static void updateTokenMap(String token){
map.put("token",token);
}
}
注册拦截器:
package com.adapter;
import com.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class LoginAdapter implements WebMvcConfigurer {
//解决跨域问题
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("Content-Type","X-Requested-With","accept,Origin","Access-Control-Request-Method","Access-Control-Request-Headers","token")
.allowedMethods("*")
.allowedOrigins("*")
//是否允许使用cookie
.allowCredentials(true);
}
@Autowired
private LoginInterceptor loginInterceptor;
// 这个方法是用来配置静态资源的,比如html,js,css,等等
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("进入拦截器");
//addPathPatterns是表明拦截哪些请求
//excludePathPatterns是对哪些请求不做拦截
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login");
}
}
以上是后台的配置,除了登陆所有请求都会进行token验证。
前端代码概要:
前端用的VUE
登陆界面最主要的是:localStorage.setItem('accessToken', response.data);把token信息存储
每次请求都放到header中:
此处简写:
Axios.post(this.addUrl,param,
{headers: {'Content-Type':'application/json;charset=UTF-8','accessToken':localStorage.getItem('accessToken')}},
{method: 'put'}
).then(response => {
localStorage.getItem('accessToken');获取存储在localStorage中的token信息