vue中通过请求头发送token到后端接收验证(解决拦截器中获取请求头信息为null,解决跨域)

本文记录了一个SpringBoot+Vue项目在实现JWT鉴权过程中遇到的跨域、请求头信息丢失等问题及解决方案。包括前端axios封装、后端JWT拦截器配置、跨域配置的详细步骤,以及OPTIONS预检请求的处理。通过问题的解决过程,加深了对Vue和SpringBoot跨域问题的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我的项目的springboot+vue前后端分离项目,在做给项目中增加JWT验证时出现了许多的问题,因此写这篇博客来记录遇到的问题以及解决方法

遇到的问题

在给项目中增加jwt的token验证的时候(将token放在请求头中),后端获取不到我在前端请求头中封装好的token信息,为了解决这个问题,查阅了许多资料,在解决问题的过程中也遇到了许多新的问题,接下来就跟大家一一讲述

下面的解决方案会默认是配置了jwt拦截器验证的,jwt拦截器不知道怎么配置的跨域去看看我的jwt学习笔记博客里面有

如果讲解的有不明白的可以私信我

跨域

最初我的项目是不存在跨域问题的,但是我问了下老师我的后端接为什么收不到请求头中的信息,老师帮我看了看,怀疑可能是我的跨域没有配置好产生的问题,然后老师发了一份他的跨域配置给我来进行配置,结果更换跨域配置后就真的是出现了跨域问题

这是报的错误信息:

The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

到了后面就算我把配置修改回来,也会报错。

经过不断的查阅资料和修改后端,我的项目终于又不会出现跨域的错误,以下讲述如何配置

  1. 在控制器中全部加入@CrossOrigin注解

    image-20220422095606932

  2. 不知道为什么我的项目就算加入了@CrossOrigin注解也还是会报错,还需要加入WebMvcConfig配置类才没报错

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOriginPatterns("*")
                    .allowedMethods("*")
                    .allowCredentials(true)
                    .maxAge(3600);
        }
    }
    
  3. 把其它配置跨域的配置取消,只需要这两个就行了

到这里,我的跨域错误问题解决了,但是我最初的问题还是没有解决——后端获取请求头中的信息为null,请往下面继续看

前端往请求头中放入数据

先给大家放上我封装好了的axios代码

import axios from 'axios';
import router from '../router';

axios.defaults.timeout = 5000;  //超市时间是5秒
axios.defaults.withCredentials = true;  //允许跨域
//Content-Type 响应头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
//基础url
axios.defaults.baseURL = "http://localhost:8888";
// axios.defaults.headers.common['Authorization'] = localStorage.token
// axios.defaults.headers.common['token'] = localStorage.token

//请求拦截器
axios.interceptors.request.use(
  config => {
    //先从浏览器的回话存储中提取token值
    const tokenStr = localStorage.getItem('token')
    if (tokenStr) {
      config.headers.Authorization=tokenStr
    }
    return config
  })
//响应拦截器
axios.interceptors.response.use(
    response => {
        //如果reponse里面的status是200,说明访问到接口了,否则错误
      console.log(response);
        // if (response.data.state==false){
        //   // alert("账号信息已过期,请重新登录");
        //   location.href = '/'
        //   localStorage.removeItem("token");
        // }
        if(response.status == 200){
            return Promise.resolve(response);
        }else{
            return Promise.reject(response);
        }

    },
    error => {
        if(error.response.status){
          console.log(error);
            switch(error.response.status){
                case 401:       //未登录
                    router.replace({
                        path:'/',
                        query:{
                            redirect: router.currentRoute.fullPath
                        }
                    });
                    break;
                case 404:   //没找到
                    break;
            }
            return Promise.reject(error.response);
        }
    }
);

/**
 * 封装get方法
 */
export function get(url,params={}){
    return new Promise((resolve,reject) => {
        axios.get(url,{params:params})
        .then(response =>{
          console.log(response.data);
          if (response.data.state==false){
            // alert("账号信息已过期,请重新登录");
            // location.href = '/'
            // this.notify("账号信息已过期,请重新登录","false");
          }
            resolve(response.data);
        })
        .catch(err =>{
            reject(err);
        })
    });
}

/**
 * 封装post方法
 */
export function post(url,data={}){
    return new Promise((resolve,reject) => {
        // axios.post(url,data,{headers:{'token': window.localStorage['token']}})
        axios.post(url,data,)
        .then(response =>{
          console.log(response.data);
          if (response.data.state==false){
            // alert("账号信息已过期,请重新登录");
            // location.href = '/'
            // this.notify("账号信息已过期,请重新登录","false");
          }
            resolve(response.data);
        })
        .catch(err =>{
            reject(err);
        })
    });
}

首先你的token数据是在登录的时候由后端返回的,然后前端接收并存储,我是选择使用localStorage来进行存储

image-20220422100451221

这个时候你的localStorage中就会存在token了,然后我们封装一个请求拦截器,让前端axios在每次给后端发送请求的时候都会携带上该请求头数据

**注意:**如果不适用请求拦截器来设置请求头中的token,而是利用全局默认请求头设置,那么你的请求头中token信息不会进行更新,就算重新登录,携带的也会是原来的token,而不是新的token。

特别注意:请求头中储存token数据的一定是Authorization,不能是其它名字

//请求拦截器
axios.interceptors.request.use(
  config => {
    //先从浏览器的localStorage存储中提取token值
    const tokenStr = localStorage.getItem('token')
    if (tokenStr) {
      config.headers.Authorization=tokenStr
    }
    return config
  })

OPTIONS的问题

首先我讲一下我是怎么发现这个问题的:我在controller中去获取我的请求头中的信息,是可以获取到的,但是在JWT验证的拦截器中无法获取得到。

原因:

实际上发送了两次请求,第一次为 OPTIONS 请求,第二次才 GET/POST… 请求在OPTIONS请求中,不会携带请求头的参数,所以在拦截器上获取请求头为空。

自定义的拦截器拦截成功第一次请求不能通过,就不能获取第二次的请求了 GET/POST…第一次请求不带参数,第二次请求才带参数

**解决:**在拦截器的最上面加上这段话

//如果请求为 OPTIONS 请求,则返回 true,否则需要通过jwt验证
 if (HttpMethod.OPTIONS.toString().equals(request.getMethod())){
     System.out.println("OPTIONS请求,放行");
     return true;
 }

image-20220422101914988

到了这里基本问题就解决了,再给大家扩展一个注意点:

拦截器需要放行静态资源

如果你是配置了虚拟路径,那么你需要放行它的路径,否则会访问不到它的资源

image-20220422102231857

最后我就把我相关的所有代码都放上来供大家参考:

全部代码

jwt工具类

public class JWTUtils {
    private static final String SIGN="!qwe@[wef";//后面从数据库中获取

    /**
     * 生成token header.payload.sign
     * 由于header有默认值,所以我们可以不需要设置,如果有需要可以进行设置
     */
    public static String getToken(Map<String,String> map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认7天过期
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //playload
        map.forEach((k,v)->{builder.withClaim(k,v);});

        String token = builder.withExpiresAt(instance.getTime())//令牌过期时间
                .sign(Algorithm.HMAC256(SIGN));//签名

        return token;
    }

    /**
     * 验证token合法性,并返回DecodedJWT对象
     */
    public static DecodedJWT verify(String token){
        return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);

    }

//    /**
//     * 获取token信息方法
//     */
//    public static DecodedJWT getTokenInfo(String token){
//        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
//        return verify;
//    }
}

jwt拦截器

@Component
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //如果请求为 OPTIONS 请求,则返回 true,否则需要通过jwt验证
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())){
            System.out.println("OPTIONS请求,放行");
            return true;
        }
        HashMap<String, Object> map = new HashMap<>();
        //获取请求头中的令牌
//        String token = request.getHeader("token");//所以前端需要将token放到请求头中
        String token = request.getHeader("Authorization");//所以前端需要将token放到请求头中
        System.out.println("请求头中的token:"+token);

        try {
            JWTUtils.verify(token);//验证令牌
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            map.put("msg","无效签名");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token过期");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            map.put("msg","token算法不一致");
        }catch (Exception e){
            e.printStackTrace();
            map.put("msg","token无效");
        }
        map.put("state",false);//设置状态
        //将map转为json 使用jackson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().print(json);
        return false;
    }
}

jwt拦截器配置

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Autowired
    private HttpInterceptor httpInterceptor;
    @Autowired
    private JWTInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor(httpInterceptor).addPathPatterns("/**");

        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/**")//对所有路径进行拦截
                .excludePathPatterns("/admin/login/status","/img/singerPic/**",
                        "/img/songListPic/**","/img/songPic/**","/avatorImages/**",
                        "/img/**");//这些路径放行

    }


}

跨域配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

前端axios封装

import axios from 'axios';
import router from '../router';

axios.defaults.timeout = 5000;  //超市时间是5秒
axios.defaults.withCredentials = true;  //允许跨域
//Content-Type 响应头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
//基础url
axios.defaults.baseURL = "http://localhost:8888";
// axios.defaults.headers.common['Authorization'] = localStorage.token
// axios.defaults.headers.common['token'] = localStorage.token

//请求拦截器
axios.interceptors.request.use(
  config => {
    //先从浏览器的回话存储中提取token值
    const tokenStr = localStorage.getItem('token')
    if (tokenStr) {
      config.headers.Authorization=tokenStr
    }
    return config
  })
//响应拦截器
axios.interceptors.response.use(
    response => {
        //如果reponse里面的status是200,说明访问到接口了,否则错误
      console.log(response);
        // if (response.data.state==false){
        //   // alert("账号信息已过期,请重新登录");
        //   location.href = '/'
        //   localStorage.removeItem("token");
        // }
        if(response.status == 200){
            return Promise.resolve(response);
        }else{
            return Promise.reject(response);
        }

    },
    error => {
        if(error.response.status){
          console.log(error);
            switch(error.response.status){
                case 401:       //未登录
                    router.replace({
                        path:'/',
                        query:{
                            redirect: router.currentRoute.fullPath
                        }
                    });
                    break;
                case 404:   //没找到
                    break;
            }
            return Promise.reject(error.response);
        }
    }
);

/**
 * 封装get方法
 */
export function get(url,params={}){
    return new Promise((resolve,reject) => {
        axios.get(url,{params:params})
        .then(response =>{
          console.log(response.data);
          if (response.data.state==false){
            // alert("账号信息已过期,请重新登录");
            // location.href = '/'
            // this.notify("账号信息已过期,请重新登录","false");
          }
            resolve(response.data);
        })
        .catch(err =>{
            reject(err);
        })
    });
}

/**
 * 封装post方法
 */
export function post(url,data={}){
    return new Promise((resolve,reject) => {
        // axios.post(url,data,{headers:{'token': window.localStorage['token']}})
        axios.post(url,data,)
        .then(response =>{
          console.log(response.data);
          if (response.data.state==false){
            // alert("账号信息已过期,请重新登录");
            // location.href = '/'
            // this.notify("账号信息已过期,请重新登录","false");
          }
            resolve(response.data);
        })
        .catch(err =>{
            reject(err);
        })
    });
}

总结

这次遇到的问题让我对vue和springboot的跨域问题理解的更加深刻,也加深了我对于vue的理解学习。解决遇到的bug的过程就是学习的过程,加油!

<think>嗯,用户问的是关于JWT获取信息和在请求拦截器中携带Token的完整代码示例。首先,我需要确认用户的需求。他们可能是在开发一个前端应用,需要实现用户认证,使用JWT来管理用户会话,并在每次请求时自动附加Token请求头中。 首先,我应该解释JWT的基本流程,用户登录后服务器返回Token,前端需要存储这个Token,通常用localStorage或者sessionStorage。然后,在每次发送请求时,尤其是在使用Axios这样的HTTP库时,通过请求拦截器自动添加Authorization头。 接下来,用户可能需要具体的代码示例。我得考虑不同的场景,比如Vue或React项目中使用Axios的设置。需要展示如何创建Axios实例,设置拦截器,以及处理Token存储和更新的情况。 还要注意安全方面,比如使用HttpOnly的Cookie存储Token可能更安全,但这里用户可能更倾向于使用localStorage。另外,处理Token过期或失效的情况,比如在响应拦截器中检查401状态码,跳转到登录页面。 可能需要分步骤说明:安装Axios,创建实例,请求拦截器添加Token,响应拦截器处理错误。同时给出登录后存储Token的示例,以及发送请求的代码。 要确保代码正确,比如在拦截器中使用config.headers.Authorization = Bearer ${token},注意Bearer后面有空格。还要提到在服务端需要验证Token,但具体的后端代码可能不在用户的问题范围内,所以简要提及即可。 可能还需要注意问题,比如在开发环境中配置代理,但用户的问题主要是前端部分,所以暂时不需要深入。 最后,检查是否遗漏了关键点,比如Token的存储位置、拦截器的正确使用、错误处理等。确保代码示例完整,用户可以直接复制并适配自己的项目。</think>我为您提供一个基于Axios的完整JWT处理方案(分步骤说明): 1. 首先安装所需依赖 ```bash npm install axios js-cookie # 或使用yarn ``` 2. 创建axios实例与拦截器配置 ```javascript // src/utils/request.js import axios from 'axios' import { getToken } from './auth' // 自定义的token获取方法 // 创建axios实例 const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 根据环境变量配置 timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config => { // 在发送请求前统一处理 const token = getToken() if (token) { config.headers['Authorization'] = `Bearer ${token}` // JWT标准格式 } return config }, error => { return Promise.reject(error) } ) // 响应拦截器(处理token失效) service.interceptors.response.use( response => { // 如果响应中有新token则更新 const newToken = response.headers['x-new-token'] if (newToken) { setToken(newToken) } return response.data }, error => { if (error.response.status === 401) { // token过期处理 removeToken() router.push('/login') } return Promise.reject(error) } ) export default service ``` 3. Token管理工具(存储/获取/删除) ```javascript // src/utils/auth.js import Cookies from 'js-cookie' const TokenKey = 'jwt_token' export function getToken() { return Cookies.get(TokenKey) || localStorage.getItem(TokenKey) } export function setToken(token) { // 根据安全策略选择存储方式 if (process.env.NODE_ENV === 'production') { Cookies.set(TokenKey, token, { expires: 7, secure: true }) } else { localStorage.setItem(TokenKey, token) } } export function removeToken() { Cookies.remove(TokenKey) localStorage.removeItem(TokenKey) } ``` 4. 登录示例 ```javascript // 在登录组件中 import { setToken } from '@/utils/auth' import request from '@/utils/request' async function login() { try { const { data } = await request.post('/api/login', { username: 'admin', password: 'password' }) // 存储token setToken(data.token) // 跳转到主页 router.push('/dashboard') } catch (error) { console.error('登录失败', error) } } ``` 5. 发起带Token的请求示例 ```javascript // 获取用户信息 async function getUserInfo() { try { const userInfo = await request.get('/api/user/info') console.log('用户信息:', userInfo) } catch (error) { console.error('获取信息失败', error) } } ``` 关键点说明: 1. 安全存储:生产环境建议使用HttpOnly的Cookie,开发环境可用localStorage 2. 自动续期:通过响应拦截器检测新token实现无感刷新 3. 错误处理:统一处理401状态码实现自动跳转登录 4. 请求头格式:严格使用`Bearer`前缀+空格+token的格式 5. 支持:确保服务端配置`Access-Control-Allow-Headers`包含Authorization 服务端需要配合: 1. 登录接口返回有效JWT 2. 受保护接口验证Authorization头 3. 设置CORS允许凭证携带(credentials: true) 这个方案已在多个生产项目中验证,建议根据实际业务需求调整token存储策略和错误处理逻辑。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的大雄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值