web前端跨域访问以及解决方案
web前端跨域访问以及解决方案
1、什么是跨域?
跨域是因为浏览器的同源策略所导致的。所谓同源是指"协议+域名+端口"三者相同,那么以上条件只要有一个不同,都被当作是不同的源,
即便两个不同的域名指向同一个ip地址,也非同源。浏览器引入同源策略主要是为了防止XSS,CSRF攻击。
同源策略限制内容有:
- Cookie、LocalStorage、IndexedDB 等存储性内容
- DOM 节点
- AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>

CSRF(Cross-site request forgery),跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
在同源策略影响下,域名A向域名B发送Ajax请求,或操作Cookie、LocalStorage、indexDB等数据,或操作dom,js就会受到限制,但请求css,js等静态资源不受限制
跨域的解决方案
1 、通过jsonp跨域
1) JSONP原理
利用 <script>
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
2) JSONP和AJAX对比
JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
3) JSONP优缺点
JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
jquery ajax请求实现
$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",//可以省略
jsonpCallback:"show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
jsonp:"callback",//->把传递函数名的那个形参callback,可省略
success:function (data){
console.log(data);}
});
jsonp缺点:只能使用get请求,不推荐使用
2、 CORS 跨域资源共享
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
服务器端对于CORS的支持,主要就是通过设置 Access-Control-Allow-Origin
来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。更多有关跨域资源共享 CORS 的知识
浏览器中可以查看对应的响应头,举个例子,如下

服务端允许CORS,服务端需要针对接口设置的一系列响应头 (Response Headers)
该字段必需。设置允许请求的域名,多个域名以逗号分隔,也可以设置成 * 即允许所有源访问 Access-Control-Allow-Origin: http://www.YOURDOMAIN.com 该字段必需。设置允许请求的方法,多个方法以逗号分隔 Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS 该字段可选。设置允许请求自定义的请求头字段,多个字段以逗号分隔 Access-Control-Allow-Headers: Authorization 该字段可选。设置是否允许发送 Cookies Access-Control-Allow-Credentials: true
该字段可选,表示每隔多久才发送一次预检请求
response.addHeader("Access-Control-Max-Age", "1800"),表示隔30分钟才发起预检请求。也就是说,发送两次请求,0表示每次都发送两次
浏览器将CORS请求分成两类:简单请求(simple request) 和 非简单请求(not-so-simple request)。
1.简单请求
目前大多数情况都采用这种方式。简单请求只需要设置Access-Control-Allow-Origin
即可。满足以下两个条件,就属于简单请求。
- 请求方法是这三种方法之一:HEAD,GET,POST
- HTTP头不超出一下几种字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
2.非简单请求
不符合以上条件的请求就肯定是复杂请求了。
复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。
/**
* 服务端通过CORS解决跨域访问
*/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");
// 非简单请求会先发送OPTIONS确认服务端是否支持CORS跨域访问
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
} else {
chain.doFilter(req, resp);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
springBoot配置
方式一、全局CORS配置:
/**
* @date 2019/08/26 22:53
*/
@Configuration
public class GlobalCorsConfig {
// 第一种方式:返回新的CorsFilter(全局跨域)
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//是否发送Cookie信息
config.setAllowCredentials(true);
//放行哪些原始域(请求方式)
config.addAllowedMethod("*");
//放行哪些原始域(头部信息)
config.addAllowedHeader("*");
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
config.addExposedHeader("*");
//2.添加映射路径
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}
方式二、使用注解(局部跨域)
// 2、在方法上注解
@Controller
public class MethodAnnotationCors {
@RequestMapping("/hello")
@ResponseBody
@CrossOrigin("http://localhost:8080")
public String index( ){
return "Hello World";
}
}
或者在控制器(@Controller)上使用注解 @CrossOrigin :
// 1、在控制器上注解
@Controller
@CrossOrigin(origins = "http://xx-domain.com", maxAge = 3600)
public class ControllerAnnotationCors {
@RequestMapping("/hello")
@ResponseBody
public String index( ){
return "Hello World";
}
}
3、使用代理跨域
中介商---代理
把这个请求转发到真正的后端域名上,那也就不存在跨域问题了。比较普遍的Nginx,简单的配置一下就可以了。
server{
# 监听9099端口
listen 9099;
# 本地的域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://baidu.com
location ^~ /api {
proxy_pass http://baidu.com;
}
}
http://localhost:9099/api/xxx
,然后Nginx监听到地址是localhost:9099/api
的请求,就帮我们转发到真正的服务端地址http://baidu.com
withCredentials: true

2: Access-Control-Allow-Origin: 不要设置为 * , 要设置成前端的域名
Access-Control-Allow-Credentials: 设置为 true
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "http://dev.boleme.net:9000");
// response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, resp);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
}