ajax跨域问题及解决方案

一、什么是跨域

  • 当前发起请求的域与该请求指向的资源所在的域不一样,就是跨域
  • 协议 + 域名 + 端口号均相同,那么就是同域,否则是跨域
  • localhost和127.0.0.1虽然都指向本机,但也属于跨域

二、ajax跨域请求失败示例

本地测试,编写前后端分离代码,在前端使用ajax请求后端接口

  • 后端接口 url http://127.0.0.1:8080/test/get
  • 前端请求 url http://127.0.0.1:8081
  • 浏览器报错信息如下在这里插入图片描述

三、产生跨域问题的原因

1、浏览器的限制
  • 跨域问题来源于JavaScript的同源策略。
  • 同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
  • 当浏览器发现是跨域请求时,就会进行一些校验,当校验不通过,就会报跨域安全问题。
  • 上面那个请求失败的示例,F12查看,发现请求返回200 ,虽然浏览器报错,但请求是成功了在这里插入图片描述
  • 可以看出,服务器后台没有进行限制的,是浏览器限制报错。
2、跨域
  • 当前发起请求的域与该请求指向的资源所在的域不一样,就会跨域失败。
  • 如果是同域请求,是不存在这个问题的。
3、XHR(XMLHttpRequest)请求
  • 跨域问题是针对JS和ajax的,html本身没有跨域问题,凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<script><img><iframe>,可以直接跨域发送数据并接收数据等

上面3个问题同时满足,才可能产生跨域问题

四、解决方案

1、解除 “浏览器的限制”

  • 通过命令行启动浏览器,指定参数,禁止浏览器检查
  • chrome --disable-web-security
  • 指定这个参数,会降低浏览器安全

2、使用JSONP请求

(1) JSONP基本原理
  • JSONP(JSON with Padding) 是 json 的一种"使用模式",为了便于客户端使用数据,逐渐形成了一种非正式传输协议
  • JSONP的基本思想是,网页通过添加一个<script>元素,通过src属性去触发对指定地址的请求,这种做法不受同源政策限制
  • 允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了
(2) JSONP调用实现
  • 假设接口 http://127.0.0.1:8080/test/get 响应为:{"data": "get ok"}, 现在需要支持jsonp,怎么办?
  • 只需要请求时, 增加一个参数 callback, 例如 http://127.0.0.1:8080/test/get?callback=abc,那么返回到客户端的响应将是: abc({"data": "get ok"})
  • 不仅客户端需要修改,服务端也需要修改
  • 服务端添加切面
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
	public JsonpAdvice() {
		super("callback"); // 与前端传的jsonp参数呼应
	}
}
  • 客户端jsonp请求
$.ajax({
	url : 'http://127.0.0.1:8080/test/get',
	datatype : 'jsonp',
	jsonp: 'callback',
	success: function(data) {
		console.log(data);
	}
});
  • JSONP请求后通过动态生成<script>脚本,为src属性指定一个跨域URL(这个导致它只能支持GET请求),生成的<script>元素如下
<script async src="http://127.0.0.1:8080/test/get?callback=jQuery111302177301635589095_1582778887373&_=1582778887374"></script>
(3) 普通ajax请求与JSONP请求区别
  • 普通的ajax请求type为xhr,而jsonp请求type为script
  • 普通的ajax请求 Content-Type 为 application/json
  • jsonp请求 Content-Type 为 application/javascript
  • jsonp请求支持GET,不支持POST

3、CORS(跨域资源共享,Cross-Origin Resource Sharing)

  • 跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。
  • 允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行
(1) 简单请求与非简单请求
  1. 满足以下条件的请求为简单请求,其余为非简单请求
  • 请求方法:GET、HEAD、POST
  • 请求头里:
    - 无自定义头
    - Content-Type 的值仅限于下列三者之一:
    text/plain
    multipart/form-data
    application/x-www-form-urlencoded
  1. 预检请求
  • 非简单请求首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求
  • "预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响
(2) CORS 相关头部信息

1、Origin

  • 请求头信息,浏览器携带的,用于表明该跨域请求的请求来源
Origin: http://127.0.0.1:8080

2、Access-Control-Allow-Origin

  • 响应头信息,由服务端返回,用于明确指定那些客户端的域名允许访问这个资源
  • 它的值可以是一个具体的域名(如 http://127.0.0.1:8081)也可以是 * 表示任意域名
Access-Control-Allow-Origin: * //该资源可以被任意外域访问
Access-Control-Allow-Origin: http://127.0.0.1:8080

3、Access-Control-Request-Method 和 Access-Control-Request-Headers

  • 请求头信息,在预检请求中携带了这两个头部字段
  • Access-Control-Request-Method: POST告知服务器,实际请求将使用 POST 方法
  • Access-Control-Request-Headers: X-PINGOTHER, Content-Type告知服务器,实际请求将携带两个自定义请求头部字段:X-PINGOTHER 与 Content-Type

4、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers

  • 响应头信息
  • Access-Control-Allow-Methods: POST, GET, OPTIONS表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求
  • Access-Control-Allow-Headers: X-PINGOTHER, Content-Type表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type

5、Access-Control-Expose-Headers

  • 响应头信息,列出了哪些首部可以作为响应的一部分暴露给外部。
  • 如果想要让客户端可以访问到其他的首部信息,可以将它们在 Access-Control-Expose-Headers 里面列出来。

6、Access-Control-Max-Age

  • 响应头信息
  • Access-Control-Max-Age: 86400表明该响应的有效时间为 86400 秒,也就是 24 小时。
  • 在有效时间内,浏览器无须为同一请求再次发起预检请求。
  • 注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

7、Access-Control-Allow-Credentials

  • 响应头信息,指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容
  • 如果跨域请求携带了cookie信息,服务端必须设置 Access-Control-Allow-Credentials: true,否则浏览器将不会把响应内容返回给请求的发送者
  • 附带身份凭证的ajax请求
$.ajax({
  url:'http://127.0.0.1:8080/test/cookie',
  type:'GET',
  xhrFields:{
    withCredentials: true // 跨域请求想要带上cookie信息必须加上
  }, 
  success:function(data){
    //请求成功相关处理
  } 
});
  • 注意,对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*,这是因为请求的首部中携带了 Cookie 信息,Access-Control-Allow-Origin 的值必须设置为具体的域名。

4、后端使用Filter添加 CORS 响应头信息

  • 配置过滤器
@Bean
public FilterRegistrationBean<CorsFilter> registerFilter() {
	FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
	bean.addUrlPatterns("/*");
	bean.setFilter(new CorsFilter());
	return bean;
}
  • 过滤器
public class CorsFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		String origin = httpServletRequest.getHeader("Origin");
		if (!StringUtils.isEmpty(origin)) {
			HttpServletResponse httpServletResponse = (HttpServletResponse) response;
			String method = httpServletRequest.getMethod();
			if (misMatchOrigin(origin) || misMatchMethod(method)) {
				rejectRequest(httpServletResponse);
				return;
			}
			// 带cookie的时候,origin必须是全匹配,不能使用*
			httpServletResponse.addHeader("Access-Control-Allow-Origin", origin);
			httpServletResponse.addHeader("Access-Control-Allow-Methods", "POST,GET,OPTION");
			// 支持自定义请求头
			String requestHeader = httpServletRequest.getHeader("Access-Control-Request-Headers");
			if (!StringUtils.isEmpty(requestHeader)) {
				httpServletResponse.addHeader("Access-Control-Allow-Headers", requestHeader);
			}
			httpServletResponse.addHeader("Access-Control-Max-Age", "3600");
			httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
			
			if (HttpMethod.OPTIONS.matches(method)) {
				return;
			}
		}
		chain.doFilter(request, response);
	}

	private void rejectRequest(HttpServletResponse httpServletResponse) throws IOException {
		httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
		httpServletResponse.getOutputStream().write("Invalid CORS request".getBytes(StandardCharsets.UTF_8));
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值