赞
踩
前端登录成功后将后端放在Response header里面Authorization字段提取出来,存入到store(这里我后端Authorization大写,浏览器响应也是大写,我前端原来也是大写但是为undefined,打印header后发现是小写authorization很奇怪)
store.commit('changeLogin',{ Authorization: res.headers['authorization']});
this.$router.push('/admin/resource')
main.js文件里面设置每次请求添加Authorization:token
axios.interceptors.request.use(
config => {
if (window.localStorage.getItem('Authorization')) {
config.headers['Authorization'] = window.localStorage.getItem('Authorization');
}
return config;
},
error => {
return Promise.reject(error);
});
但是这时请求会出现跨域问题,(不加时正常访问)
Access to XMLHttpRequest at ‘http://xxxxxxxx’ from origin ‘http://xxxxxxxx’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
这是因为浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
1、简单请求
只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:HEAD、GET、POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
由于我向请求头中添加了自定义的属性,所以发送请求时就属于非简单请求。
2、预检请求
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为 “预检” 请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
从上图中可以看出浏览器发送了两次getAllImages请求,一次操作方法为OPTIONS,一次为GET。OPTIONS请求即为预检请求,用于判断后端服务是否能接受OPTIONS请求,注意这个请求是没有Auh字段的。如果没在Header里面添加Auth字段则为一次普通的GET请求。由于后端拦截器逻辑获取Auth字段导致为null,抛出异常,不能执行完整的 filterChain.doFilter导致浏览器认为OPTIONS操作不允许,所以出现上述跨域问题。
try {
String token = httpServletRequest.getHeader("Authorization");
System.out.println("token" + token);
stringRedisTemplate.opsForSet().isMember("Token", token);
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (Exception e) {
Map map = new HashMap<>();
map.put("code", "401");
map.put("message", "无权限");
PrintWriter out = httpServletResponse.getWriter();
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
System.out.println(e);
}
}
OPTIONS请求失败,我全局配置的CORS应该是晚于自定义的handler,所以出现了即使CORS配置了OPTIONS操作的许可,还是出现跨域问题了。
我的解决办法加一次判断如果是OPTIONS直接放行dofilter操作,这样的话浏览器OPTIONS预请求成功,就能发送带Auth的GET请求
if (!httpServletRequest.getRequestURI().equals("/login") && !httpServletRequest.getRequestURI().equals("/getAllTag") && !httpServletRequest.getRequestURI().equals("/getAllImage")) {
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
httpServletResponse.setContentType("application/json;charset=utf-8");
try {
this.stringRedisTemplate.opsForSet().isMember("Token", token);
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (Exception var7) {
Map map = new HashMap();
map.put("code", "401");
map.put("message", "无权限");
PrintWriter out = httpServletResponse.getWriter();
out.write((new ObjectMapper()).writeValueAsString(map));
out.flush();
out.close();
}
}
} else {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
或者简单Security配置一下
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
//跨域请求会先进行一次options请求
.antMatchers(HttpMethod.OPTIONS).permitAll()
...
}
后面我像设置一下别处登录后上一个登录的终端请求失效
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if (httpServletRequest.getRequestURI().equals("/login") || httpServletRequest.getRequestURI().equals("/getAllTag")
|| httpServletRequest.getRequestURI().equals("/getAllImage")
|| httpServletRequest.getRequestURI().equals("/vercode")) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
try{
httpServletResponse.setContentType("application/json;charset=utf-8");
String token = httpServletRequest.getHeader("Authorization");
Claims claims = jwtUtil.getClaimsByToken(token);
Boolean redisToken = redisTemplate.opsForHash().hasKey("Token", claims.get("sub", String.class));
if (redisToken) {
if (token.equals(redisTemplate.opsForHash().get("Token", claims.get("sub", String.class)))) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
Map map = new HashMap<>();
map.put("code", "403");
map.put("message", "账户已在别处登录");
PrintWriter out = httpServletResponse.getWriter();
out.write((new ObjectMapper()).writeValueAsString(map));
out.flush();
out.close();
}
}
} catch (Exception e){
System.out.println(e);
Map map = new HashMap<>();
map.put("code", "401");
map.put("message", "无权限");
PrintWriter out = httpServletResponse.getWriter();
out.write((new ObjectMapper()).writeValueAsString(map));
out.flush();
out.close();
}
}
}
又出现需要认证接口的跨域问题了
No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.
后面查阅资料发现可以自定义一个requestfilter,header添加上Access-Control-Allow-Origin
@WebFilter(filterName = "requestFilter", urlPatterns = {"/*"})
public class RequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 此处 setHeader、addHeader 方法都可用。但 addHeader时写多个会报错:“...,but only one is allowed”
response.setHeader("Access-Control-Allow-Origin", "*");
// response.addHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
// 解决预请求(发送2次请求),此问题也可在 nginx 中作相似设置解决。
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Cache-Control,Pragma,Content-Type,Token, Content-Type");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
String method = request.getMethod();
if (method.equalsIgnoreCase("OPTIONS")) {
servletResponse.getOutputStream().write("Success".getBytes("utf-8"));
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
}
我就想着直接在自定义tokenfilter中需要验证访问接口逻辑返回段添加 httpServletResponse.setHeader(“Access-Control-Allow-Origin”, “*”);
if (redisToken) {
if (token.equals(redisTemplate.opsForHash().get("Token", claims.get("sub", String.class)))) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
Map map = new HashMap<>();
map.put("code", "403");
map.put("message", "账户已在别处登录");
System.out.println("hahahahahahah");
PrintWriter out = httpServletResponse.getWriter();
out.write((new ObjectMapper()).writeValueAsString(map));
out.flush();
out.close();
}
}
发现前端访问不跨域了。
查找原因:
界面不需要认证的接口请求:
需要认证的接口
对比两种GET请求 发现getAllImages请求下返回响应头没有 Access-Control-Allow-Origin: http://localhost:8080
字段所以造出浏览器跨域问题。
检查后端代码发现成功的接口都完整执行了filterChain.doFilter(httpServletRequest, httpServletResponse);
所以猜测自定义的tokenfilter在securityfilter中添加这个响应头的过滤器之前,这里自定义TokenFilter验证接口验证失败直接执行了httpservletresponse.getWriter()方法返回响应,导致后面的过滤器链给响应添加 Access-Control-Allow-Origin
的方法为执行,所以造成了跨域问题,即使自己已经全局配置了跨域。
main.js文件配置请求和响应拦截器:
axios.interceptors.response.use(
res=>{
console.log(res)
if( res.data.code == 403){
alert("!!已在其他终端登录,请重新登录!!")
window.localStorage.clear
//记住是router 和上面router.beforeEach一样,此时app还未喧染。this不能用,$不能用
router.push("/login")
}else if(res.data.code == 401){
alert("!!无权限!!")
return res;
}else{
return res;
}
},
err=>{
alert("!!无权限!!")
}
)
//请求拦截
axios.interceptors.request.use(
config => {
if (window.localStorage.getItem('Authorization')) {
console.log(window.localStorage.getItem('Authorization')+"679t7867876")
config.headers['Authorization'] = window.localStorage.getItem('Authorization');
}
return config;
},
error => {
return Promise.reject(error);
});
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。