Tch
Tch
Published on 2024-11-04 / 34 Visits
0
0

SpringBoot+Vue3前后端分离项目中Token的使用

Token的使用方式

在SpringBoot+Vue3前后端分离项目中,为了保证用户会话的安全性和有效性,通常会在用户登录成功后生成一个Token返回给前端,并在每次请求时都在请求头中附带这个Token,验证用户的身份,请求成功后重新生成一个Token并返回。

前端实现步骤

  1. 发起请求时,从浏览器缓存(localStorage)中读取Token并加入到请求头中,发送给服务器。

  2. 接收来自服务端的响应结果,从响应头中获取新生成的Token,并更新localStorage中存储的Token。

  3. 如果响应结果表示Token无效,则重定向到登录页面,要求重新登录。

  4. 如果是登录请求,发起请求时不携带Token,登录成功后存储Token。

后端实现步骤

  1. 每次接收到前端发来的请求,首先验证Token的有效性(登录请求无需验证)。如果有效,则继续处理业务逻辑,如果无效,则直接返回错误信息给前端。

  2. 请求成功后,生成新的Token添加到响应头中返回给前端。

Token工具类的代码实现

有关生成和校验Token的方法参考文章 https://tch.cool/archives/lSrBv05Q

前端代码实现

创建一个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的生成、校验和存储的方式都不是固定的,本文只是提供一种打通前后端的代码实现,更多方式可自行探索。


Comment