Token的使用方式
在SpringBoot+Vue3前后端分离项目中,为了保证用户会话的安全性和有效性,通常会在用户登录成功后生成一个Token返回给前端,并在每次请求时都在请求头中附带这个Token,验证用户的身份,请求成功后重新生成一个Token并返回。
前端实现步骤
发起请求时,从浏览器缓存(localStorage)中读取Token并加入到请求头中,发送给服务器。
接收来自服务端的响应结果,从响应头中获取新生成的Token,并更新localStorage中存储的Token。
如果响应结果表示Token无效,则重定向到登录页面,要求重新登录。
如果是登录请求,发起请求时不携带Token,登录成功后存储Token。
后端实现步骤
每次接收到前端发来的请求,首先验证Token的有效性(登录请求无需验证)。如果有效,则继续处理业务逻辑,如果无效,则直接返回错误信息给前端。
请求成功后,生成新的Token添加到响应头中返回给前端。
Token工具类的代码实现
有关生成和校验Token的方法参考文章
前端代码实现
创建一个axios实例。
// create an axios instance
const service = axios.create({
baseURL: 'http://localhost:8882',
timeout: 10000
})
请求拦截器,在发起请求前从localStorage中获取Token添加到请求头中。
// request interceptor
service.interceptors.request.use(
config => {
// Retrieve token from localStorage
const token = localStorage.getItem('access-token')
if (token) {
config.headers['access-token'] = token
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
响应拦截器,接收来自服务端的响应,从响应头中获取新生成的Token,存储到localStorage中。
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
if (res.code === 401) {
// 重新登录 省略
}
if (res.code !== 200) {
// 错误信息提示 省略
return Promise.reject(new Error(res.message || 'Error'))
} else {
// 保存token
localStorage.setItem('access-token', response.headers['access-token'])
return res
}
},
error => {
console.log('err' + error)
// 错误信息提示 省略
return Promise.reject(error)
}
)
后端代码实现
自定义请求拦截器,Token校验不通过时直接返回false,不继续处理业务逻辑。
/**
* @author denchouka
* @description 自定义拦截器
* @date 2024/10/26 20:41
*/
public class CustomInterceptor implements HandlerInterceptor {
/**
* 在请求处理之前做一些事情
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// access-control-request-headers是跨域时候的预检请求,跳过
boolean equals = StringUtils.equalsIgnoreCase(REQUEST_METHOD_PRE_FLIGHT, request.getMethod());
boolean blank = StringUtils.isBlank(request.getHeader(REQUEST_HEADER_PRE_FLIGHT));
if(!blank && equals){
return true;
}
// 登录的url可直接访问
if (StringUtils.equals(REQUEST_URL_LOGIN, request.getRequestURI())) {
return true;
}
// 验证token
if(!TokenUtils.verify(request.getHeader(REQUEST_HEADER_ACCESS_TOKEN))){
return false;
}
return true;
}
}
在自定义配置中配置前端拦截器
/**
* @author denchouka
* @description 自定义配置
* @date 2024/10/26 18:46
*/
@Configuration
public class CustomConfig implements WebMvcConfigurer {
/**
* 跨域配置
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 省略
}
/**
* 配置拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor())
.addPathPatterns("/**");
}
}
对全局的响应进行处理
/**
* @author denchouka
* @description 对全局响应进行处理
* @date 2024/11/1 20:38
*/
@ControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice {
/**
* 指定哪些类型的响应需要处理
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// true表示处理所有类型的响应
return true;
}
/**
* 在写入响应体之前执行
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 获取userName(获取方式省略)
String userName = null;
// ip地址(获取方式省略)
String ip = null;
// 生成新的Token
String token = TokenUtils.token(userName, ip);
// 返回新的token
HttpHeaders responseHeaders = response.getHeaders();
responseHeaders.set(ACCESS_CONTROL_EXPOSE_HEADERS, REQUEST_HEADER_ACCESS_TOKEN);
responseHeaders.set(REQUEST_HEADER_ACCESS_TOKEN, token);
return body;
}
}
写在最后
以上就是在前后端分离项目中一种简单的使用Token的思路。不管是每一次请求成功后都更新Token,还是只在登录成功后返回Token并在每一次请求时使用该Token做校验都可以,具体还是要以实际业务场景为主。
而Token的生成、校验和存储的方式都不是固定的,本文只是提供一种打通前后端的代码实现,更多方式可自行探索。