我的项目的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.
到了后面就算我把配置修改回来,也会报错。
经过不断的查阅资料和修改后端,我的项目终于又不会出现跨域的错误,以下讲述如何配置
-
在控制器中全部加入@CrossOrigin注解
-
不知道为什么我的项目就算加入了@CrossOrigin注解也还是会报错,还需要加入WebMvcConfig配置类才没报错
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("*") .allowCredentials(true) .maxAge(3600); } }
-
把其它配置跨域的配置取消,只需要这两个就行了
到这里,我的跨域错误问题解决了,但是我最初的问题还是没有解决——后端获取请求头中的信息为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来进行存储
这个时候你的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;
}
到了这里基本问题就解决了,再给大家扩展一个注意点:
拦截器需要放行静态资源如果你是配置了虚拟路径,那么你需要放行它的路径,否则会访问不到它的资源
最后我就把我相关的所有代码都放上来供大家参考:
全部代码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的过程就是学习的过程,加油!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)