• 【JS】AJAX跨域-被调用方与调用方解决方案(二)


    解决跨域问题

      跨域问题说明,参考【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 }
    View Code

        

    解决方案三(被调用方支持跨域-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>

      

      

  • 相关阅读:
    记好这24个ES6方法,用于解决实际开发的JS问题
    es6 扩展运算符 剩余运算符 ...
    Django基础006--在pycharm中将项目配置为Django项目
    Django基础005-Django开发的整体过程
    Django基础-004 上下文管理器&中间件&前端公共代码复用
    Django基础-003 配置Django自带的后台管理,操作数据库
    Django基础-002 Models的属性与字段
    jconsole和jstack
    Django基础-001
    前端009-vue框架
  • 原文地址:https://www.cnblogs.com/h--d/p/11478967.html
Copyright © 2020-2023  润新知