解决跨域问题
跨域问题说明,参考【JS】AJAX跨域-JSONP解决方案(一)
实例,使用上一章(【JS】AJAX跨域-JSONP解决方案(一))的实例
解决方案三(被调用方支持跨域-服务端代码解决)
被调用方解决,基于支持跨域的解决思路,基于Http协议关于跨域的相关规定,在响应头里增加指定的字段告诉浏览器,允许调用
跨域请求是直接从浏览器发送到被调用方,被调用方在响应头里增加相关信息,返回到页面,页面能正常获取请求内容。
1、服务端增加一个过滤器(CrossFilter.java),过滤所有请求,在请求响应中增加内容,如下:
1 package com.test.ajax.cross.filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.http.Cookie; 12 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletResponse; 14 15 import org.springframework.util.StringUtils; 16 17 /** 18 * 服务端解决跨域 19 * @author h__d 20 * 21 */ 22 public class CrossFilter implements Filter { 23 24 @Override 25 public void init(FilterConfig filterConfig) throws ServletException { 26 // TODO Auto-generated method stub 27 28 } 29 30 @Override 31 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 32 throws IOException, ServletException { 33 HttpServletResponse res = (HttpServletResponse) response; 34 35 HttpServletRequest req = (HttpServletRequest) request; 36 37 38 // 允许所有域,但不能满足带 cookie 的跨域请求 39 res.addHeader("Access-Control-Allow-Origin","*"); 40 // 允许所有header 41 res.addHeader("Access-Control-Allow-Headers","*"); 42 // 允许所有方法 43 res.addHeader("Access-Control-Allow-Methods", "*"); 44 // 允许浏览器在一个小时内,缓存跨域访问信息(即上面三个信息) 45 res.addHeader("Access-Control-Max-Age", "3600"); 46 47 chain.doFilter(request, response); 48 49 } 50 51 @Override 52 public void destroy() { 53 // TODO Auto-generated method stub 54 55 } 56 57 }
2、在web.xml文件中注册过滤器
<filter> <filter-name>CrossFilter</filter-name> <filter-class>com.test.ajax.cross.filter.CrossFilter</filter-class> </filter> <filter-mapping> <filter-name>CrossFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3、编辑测试界面,test3.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Insert title here</title> 6 <script src="jquery-1.11.3.min.js" type="text/javascript"></script> 7 </head> 8 <body> 9 <h2>测试服务端解决跨域问题</h2> 10 <a href="#" onclick="get()">发送get请求</a> 11 </body> 12 <script type="text/javascript"> 13 function get(){ 14 $.getJSON("http://localhost:8080/test-ajax-cross/test/get").then(function(result){ 15 console.log(result); 16 $("body").append("<br>" + JSON.stringify(result)); 17 }); 18 } 19 </script> 20 </html>
4、在浏览器中输入地址进行访问,http://a.com:8080/test-ajax-cross/static/test3.html
其他请求
简单请求与非简单请求
-
-
- 简单请求:浏览器先发送真正的请求后检查
- 请求方法:GET、HEAD、POST的一种
- 请求header:无自定义header;Content-Type为:text/plain、multipart/form-data、application/x-www-form-urlencoded的一种
- 非简单请求:浏览器先发预检命令,检查通过后,才发送真正的请求
- 常见的有:PUT、DELETE
- 其它条件:发送Json格式的请求、带自定义header的请求
- 预检命令:浏览器检测到跨域请求, 会自动发出一个OPTIONS请求, 就是所谓的预检(preflight)请求。当预检请求通过的时候,才发送真正的请求。
-
A、带cookie的ajax请求,跨域问题
1、编辑test3.html页面,增加一个带cookie的ajax请求
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Insert title here</title> 6 <script src="jquery-1.11.3.min.js" type="text/javascript"></script> 7 </head> 8 <body> 9 <h2>测试服务端解决跨域问题</h2> 10 <a href="#" onclick="get()">发送get请求</a> 11 <a href="#" onclick="getCookie()">发送getCookie请求</a> 12 </body> 13 <script type="text/javascript"> 14 function get(){ 15 $.getJSON("http://localhost:8080/test-ajax-cross/test/get").then(function(result){ 16 console.log(result); 17 $("body").append("<br>" + JSON.stringify(result)); 18 }); 19 } 20 // 测试带上cookie的请求能否跨域 21 function getCookie(){ 22 $.ajax({ 23 url: "http://localhost:8080/test-ajax-cross/test/getCookie", 24 xhrFields:{ 25 // 带上证书,发送 AJAX 请求时带上 cookie 26 withCredentials:true 27 }, 28 // 允许跨域 29 crossDomain: true, 30 success:function(result){ 31 console.log(result); 32 $("body").append("<br>" + JSON.stringify(result)); 33 } 34 }); 35 } 36 </script> 37 </html>
2、增加接受请求的方法
1 @RequestMapping(value = "/getCookie", method = RequestMethod.GET) 2 @ResponseBody 3 public Map getCookie(@CookieValue(value = "cookie1") String cookie1) { 4 System.out.println("TestController getCookie()"); 5 Map<String, Object> map = new HashMap(); 6 map.put("data", "getCookie" + cookie1); 7 return map; 8 }
3、编辑过滤器,支持带cookie的ajax请求
1 package com.test.ajax.cross.filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 14 import org.springframework.http.HttpStatus; 15 import org.springframework.util.StringUtils; 16 import org.springframework.web.bind.annotation.RequestMethod; 17 18 /** 19 * 服务端解决跨域 20 * 21 * @author h__d 22 * 23 */ 24 public class CrossFilter implements Filter { 25 26 @Override 27 public void init(FilterConfig filterConfig) throws ServletException { 28 // TODO Auto-generated method stub 29 30 } 31 32 @Override 33 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 34 throws IOException, ServletException { 35 HttpServletResponse res = (HttpServletResponse) response; 36 37 HttpServletRequest req = (HttpServletRequest) request; 38 39 40 // 支持所有域 41 String origin = req.getHeader("Origin"); 42 if (!StringUtils.isEmpty(origin)) { 43 // 支持任何域名的跨域调用 且 支持带cookie(是被调用方域名的cookie,而不是调用方的cookie) 44 res.addHeader("Access-Control-Allow-Origin", origin); 45 } 46 // 指定允许的域,带cookie时,origin必须是全匹配,不能使用 * 47 // res.addHeader("Access-Control-Allow-Origin","http://localhost:8081"); 48 // 允许所有域,但不能满足带 cookie 的跨域请求 49 // res.addHeader("Access-Control-Allow-Origin","*"); 50 51 // 支持所有header 52 res.addHeader("Access-Control-Allow-Headers","*"); 53 54 // 指定允许的方法 55 // res.addHeader("Access-Control-Allow-Methods","GET"); 56 // 允许所有方法 57 res.addHeader("Access-Control-Allow-Methods", "*"); 58 // 允许浏览器在一个小时内,缓存跨域访问信息(即上面三个信息) 59 res.addHeader("Access-Control-Max-Age", "3600"); 60 61 // 允许证书,启用 cookie 62 res.addHeader("Access-Control-Allow-Credentials", "true"); 63 64 65 chain.doFilter(request, response); 66 67 } 68 69 @Override 70 public void destroy() { 71 // TODO Auto-generated method stub 72 73 } 74 75 }
4、在localhost下,增加一个cookie,方法是:浏览器打开localhost:8080,按F12打开控制台,输入:document.cookie="cookie1=test"
5、浏览器访问http://a.com:8080/test-ajax-cross/static/test3.html#,点击发送getCookie请求,可以看到控制台报错
6、解决,响应是"Access-Control-Allow-Origin",不能用"*",通配符代替。在请求头中我们可以看到有Origin字段,后端服务可以获取这个字段的值,然后设置未允许
编辑后端过滤器
7、浏览器访问http://a.com:8080/test-ajax-cross/static/test3.html#,点击发送getCookie请求,可以看到已经能正常返回
B、带自定义头的ajax请求,跨域问题
1、编辑test3.html页面,增加一个带自定义头的ajax请求
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Insert title here</title> 6 <script src="jquery-1.11.3.min.js" type="text/javascript"></script> 7 </head> 8 <body> 9 <h2>测试服务端解决跨域问题</h2> 10 <a href="#" onclick="get()">发送get请求</a> 11 <a href="#" onclick="getCookie()">发送getCookie请求</a> 12 <a href="#" onclick="getHeader()">发送getHeader请求</a> 13 </body> 14 <script type="text/javascript"> 15 function get(){ 16 $.getJSON("http://localhost:8080/test-ajax-cross/test/get").then(function(result){ 17 console.log(result); 18 $("body").append("<br>" + JSON.stringify(result)); 19 }); 20 } 21 // 测试带上cookie的请求能否跨域 22 function getCookie(){ 23 $.ajax({ 24 url: "http://localhost:8080/test-ajax-cross/test/getCookie", 25 xhrFields:{ 26 // 带上证书,发送 AJAX 请求时带上 cookie 27 withCredentials:true 28 }, 29 // 允许跨域 30 crossDomain: true, 31 success:function(result){ 32 console.log(result); 33 $("body").append("<br>" + JSON.stringify(result)); 34 } 35 }); 36 } 37 // 测试带上不同header的请求能否跨域 38 function getHeader(){ 39 $.ajax({ 40 url: "http://localhost:8080/test-ajax-cross/test/getHeader", 41 headers:{ 42 "x-header1":"AAA" 43 }, 44 beforeSend:function(xhr){ 45 xhr.setRequestHeader("x-header2","BBB") 46 }, 47 success:function(result){ 48 console.log(result); 49 $("body").append("<br>" + JSON.stringify(result)); 50 } 51 }); 52 } 53 </script> 54 </html>
2、增加自定义头的ajax请求,接受请求的方法
1 @RequestMapping(value = "/getHeader", method = RequestMethod.GET) 2 @ResponseBody 3 public Map getHeader( 4 @RequestHeader("x-header1") String header1, 5 @RequestHeader("x-header2") String header2) { 6 System.out.println("TestController getHeader()"); 7 Map<String, Object> map = new HashMap(); 8 map.put("data", "getHeader" + header1+header2); 9 return map; 10 }
3、浏览器访问http://a.com:8080/test-ajax-cross/static/test3.html#,点击发送getHeader请求,可以看到已经能正常返回
打开F12,查看Network中,发现浏览器做了2次请求,第一是请求method是options,就是所谓的预检(preflight)请求,才发送真正的请求。且第一次OPTIONS请求,headers是不会带到后端服务器上来
附:完整版过滤器
1 package com.test.ajax.cross.filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 14 import org.springframework.http.HttpStatus; 15 import org.springframework.util.StringUtils; 16 import org.springframework.web.bind.annotation.RequestMethod; 17 18 /** 19 * 服务端解决跨域 20 * 21 * @author h__d 22 * 23 */ 24 public class CrossFilter implements Filter { 25 26 @Override 27 public void init(FilterConfig filterConfig) throws ServletException { 28 // TODO Auto-generated method stub 29 30 } 31 32 @Override 33 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 34 throws IOException, ServletException { 35 HttpServletResponse res = (HttpServletResponse) response; 36 37 HttpServletRequest req = (HttpServletRequest) request; 38 39 40 // 支持所有域 41 String origin = req.getHeader("Origin"); 42 if (!StringUtils.isEmpty(origin)) { 43 // 支持任何域名的跨域调用 且 支持带cookie(是被调用方域名的cookie,而不是调用方的cookie) 44 res.addHeader("Access-Control-Allow-Origin", origin); 45 } 46 // 指定允许的域,带cookie时,origin必须是全匹配,不能使用 * 47 // res.addHeader("Access-Control-Allow-Origin","http://localhost:8081"); 48 // 允许所有域,但不能满足带 cookie 的跨域请求 49 // res.addHeader("Access-Control-Allow-Origin","*"); 50 51 // 允许所有header,但是第一次OPTIONS请求,headers是不会带过来的 52 // String headers = req.getHeader("Access-Control-Allow-Headers"); 53 // if (!StringUtils.isEmpty(headers)) { 54 // // 允许所有header,自定义头访问出现问题 55 // res.addHeader("Access-Control-Allow-Headers", headers); 56 // } 57 // 支持所有header 58 res.addHeader("Access-Control-Allow-Headers","*"); 59 60 // 指定允许的方法 61 // res.addHeader("Access-Control-Allow-Methods","GET"); 62 // 允许所有方法 63 res.addHeader("Access-Control-Allow-Methods", "*"); 64 // 允许浏览器在一个小时内,缓存跨域访问信息(即上面三个信息) 65 res.addHeader("Access-Control-Max-Age", "3600"); 66 // 允许证书,启用 cookie 67 res.addHeader("Access-Control-Allow-Credentials", "true"); 68 69 // 第一次OPTIONS请求,headers是不会带过来的 70 if (req.getMethod().equals(RequestMethod.OPTIONS.name())) { 71 res.setStatus(HttpStatus.OK.value()); 72 } 73 74 chain.doFilter(request, response); 75 76 } 77 78 @Override 79 public void destroy() { 80 // TODO Auto-generated method stub 81 82 } 83 84 }
解决方案三(被调用方支持跨域-Spring框架解决)
Spring Framework 4.2 GA为CORS提供了第一类支持。所以springMVC的版本要在4.2或以上版本才支持@CrossOrigin,@CrossOrigin可以用在类上和方法上
@CrossOrigin的作用就相当于上例中的CrossFilter.class
1 @Controller 2 @RequestMapping("/test") 3 @CrossOrigin 4 public class TestController { 5 ... 6 }
解决方案三(被调用方支持跨域-代理方案解决)
在很多项目的架构中,都是使用了代理的机制,浏览器访问nginx,nginx访问tomcat服务,如下图:
那么在上例中可以想到,跨域请求是直接从浏览器发送到被调用方,被调用方在响应头里增加相关信息,可以在tomcat应用服务程序中加,也可以在nginx/apache等代理中加
相当于nginx/apache做了过滤器的功能
流程:
1、修改页面访问地址,统一请求到http://b.com/test-ajax-cross,让请求经过代理服务器nginx/apache
2、修改代理服务器nginx/apache配置。
Nginx配置如下:
1 server{ 2 listen 80; 3 server_name b.com; 4 5 location /{ 6 proxy_pass http://localhost:8080/; 7 8 add_header Access-Control-Allow-Methods *; 9 add_header Access-Control-Max-Age 3600; 10 add_header Access-Control-Allow-Credentials true; 11 12 add_header Access-Control-Allow-Origin $http_origin; 13 add_header Access-Control-Allow-Headers *; 14 15 # 预检请求,直接返回正确 16 if ($request_method = OPTIONS){ 17 return 200; 18 } 19 } 20 }
Apache配置
- 修改conf/httpd.conf找到LoadModule vhost_alias_module module/mod_vhost_alias.so取消注释
- 修改conf/httpd.conf找到LoadModule proxy_module module/mod_ proxy.so取消注释
- 修改conf/httpd.conf找到LoadModule proxy_http_module module/mod_ proxy_http.so取消注释
- 修改conf/httpd.conf找到LoadModule headers_module module/mod_ headers.so取消注释
- 修改conf/httpd.conf找到LoadModule rewrite_module module/mod_ rewrite.so取消注释
- 修改conf/httpd.conf找到Include conf/extra/httpd-vhosts.conf取消注释
- 修改conf/extra/httpd-vhosts.conf在最后面增加下面的内容即可
<VirtualHost *:80> ServerName b.com ErrorLog "logs/b.com-error.log" CustomLog "logs/b.com-access.log" common ProxyPass / http://localhost:8080/ # 把请求头的origin值返回到Access-Control-Allow-Origin字段 Header always set Access-Control-Allow-Origin "expr=%{req:origin}" Header always Access-Control-Allow-Headers "*" Header always set Access-Control-Allow-Methods "*"; Header always set Access-Control-Max-Age "3600"; Header always set Access-Control-Allow-Credentials ""true"; # 处理预检命令OPTIONS,直接返回204 RewriteEngine On RewriteCond %{REQUEST_METHOD}OPTIONS RewriteRule ^(.*)$"/" [R=204,L] </VirtualHost>
解决方案四(被调用方支持跨域)
使用nginx进行反向代理,结构如下
当页面要进行跨域请求是,可以由nginx对url进行过滤,跨域的url请求,转发到其他服务器上,进行处理,前端无感知
nginx配置如下:
1 server{ 2 listen 80; 3 server_name a.com; 4 5 location /{ 6 proxy_pass http://127.0.0.1:8080/; 7 } 8 9 location /test-ajax-cross/test{
# 也可以转发到其他域名,进行跨域请求 10 proxy_pass http://localhost:8080/test-ajax-cross/test; 11 } 12 }
1、编辑一个页面test4.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Insert title here</title> 6 <script src="jquery-1.11.3.min.js" type="text/javascript"></script> 7 </head> 8 <body> 9 <h2>测试服务端解决跨域问题</h2> 10 <a href="#" onclick="get()">发送get请求</a> 11 <a href="#" onclick="getCookie()">发送getCookie请求</a> 12 <a href="#" onclick="getHeader()">发送getHeader请求</a> 13 </body> 14 <script type="text/javascript"> 15 function get(){ 16 $.getJSON("http://a.com/test-ajax-cross/test/get").then(function(result){ 17 console.log(result); 18 $("body").append("<br>" + JSON.stringify(result)); 19 }); 20 } 21 // 测试带上cookie的请求能否跨域 22 function getCookie(){ 23 $.ajax({ 24 url: "http://a.com/test-ajax-cross/test/getCookie", 25 xhrFields:{ 26 // 带上证书,发送 AJAX 请求时带上 cookie 27 withCredentials:true 28 }, 29 // 允许跨域 30 crossDomain: true, 31 success:function(result){ 32 console.log(result); 33 $("body").append("<br>" + JSON.stringify(result)); 34 } 35 }); 36 } 37 // 测试带上不同header的请求能否跨域 38 function getHeader(){ 39 $.ajax({ 40 url: "http://a.com/test-ajax-cross/test/getHeader", 41 headers:{ 42 "x-header1":"AAA" 43 }, 44 beforeSend:function(xhr){ 45 xhr.setRequestHeader("x-header2","BBB") 46 }, 47 success:function(result){ 48 console.log(result); 49 $("body").append("<br>" + JSON.stringify(result)); 50 } 51 }); 52 } 53 </script> 54 </html>
2、配置nginx做反向代理
3、使用地址 http://a.com/test-ajax-cross/static/test4.html#,进行访问,测试是能正常访问的
Apache反向代理实现
配置如下:
1 <VirtualHost *:80> 2 ServerName a.com 3 ErrorLog "logs/a.com-error.log" 4 CustomLog "logs/a.com-access.log" common 5 ProxyPass / http://127.0.0.1:8080/ 6 ProxyPass /ajaxserverapache http://localhost:8080/test-ajax-cross/test 7 </VirtualHost>