原文地址:xeblog.cn/articles/12html
先后端分离的项目虽然下降了耦合度,可是引起的各类问题也随之而来。后端项目由Tomcat部署(监听8080端口),前端项目则部署在Nginx上(监听80、443等非8080端口),前端页面加载速度大大提升了,而当ajax请求后端接口的时候却报错了。前端
同源策略,它是由Netscape提出的一个著名的安全策略。如今全部支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。ajax
前端地址: http://127.0.0.1:63344
后端
后端地址: http://127.0.0.1:8080
跨域
这两个地址虽然ip地址和协议都同样,可是端口不同,因此并不知足同源,这样就形成了跨域的问题。浏览器
新增一个类实现 WebMvcConfigurer
接口,而后给这个类加上 @Configuration
注解,最后实现 addCorsMappings
方法就ok了。安全
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
复制代码
请求正常bash
这就完事了?这么简单的吗?服务器
登录后台管理看看cookie
wtf?
按照个人逻辑,上面那个报错的接口应该是一个GET请求,可为何发的是OPTIONS请求?翻阅各类资料,各类百度google,各类倒腾,各类头疼,最后。。。我掀开了被子,惊奇的发现。。。被窝可真暖和啊!(先睡一觉再说)
原来浏览器会在发送真正请求以前,先发送一个方法为OPTIONS的预检请求 Preflighted requests
这个请求是用来验证本次请求是否安全的,并且并非全部请求都会发送,须要符合如下条件:
对于管理端的接口,我有对接口进行权限校验,每次请求须要在header中携带自定义的字段(token),因此浏览器会多发送一个OPTIONS请求。
那为何OPTIONS请求报错了。。。
通过debug发现,OPTIONS请求只会携带自定义的字段,并不会将相应的值带入进去,然后台校验token字段时 token为NULL
,因此验证不经过,抛出了一个异常。
我换了一种解决跨域的方法,使用拦截器解决跨域问题,而且针对OPTIONS请求作放行处理。
新增一个拦截器类 CorsInterceptor
实现 HandlerInterceptor
接口
@Component
public class CorsInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "86400");
response.setHeader("Access-Control-Allow-Headers", "*");
// 若是是OPTIONS则结束请求
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
return true;
}
}
复制代码
需将跨域拦截器放在校验拦截器之上
我把原来的跨域配置 addCorsMappings
删除了
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
@Resource
private CorsInterceptor corsInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 跨域拦截器需放在最上面
registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
// 校验token的拦截器
registry.addInterceptor(loginInterceptor).addPathPatterns("/admin/**");
}
}
复制代码
OPTIOINS请求没问题了
也相继发送了GET请求
晚安!
Access-Control-Allow-Credentials
响应头表示是否能够将对请求的响应暴露给页面。返回 true
则能够,其余值均不能够。Credentials能够是 cookies, authorization headers
或 TLS client certificates
。若是被设置为true,服务器不得设置 Access-Control-Allow-Origin
的值为 *
,须要指定域名,不然当须要发送 Cookie
到服务器时,浏览器响应时将会报错。
Ajax请求开启 withCredentials
属性
xhrFields: {
withCredentials: true
}
复制代码
后端跨域配置不变时,浏览器请求结果报错
指定 Access-Control-Allow-Origin
的值为 http://127.0.0.1:63344
,再次请求后正常
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 此处配置的是容许任意域名跨域请求,可根据需求指定
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "86400");
response.setHeader("Access-Control-Allow-Headers", "*");
// 若是是OPTIONS则结束请求
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
return true;
}
复制代码