• SpringBoot处理跨域的四种方式


    一、介绍

    1.1 为什么会出现跨域?

      出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port

    1.2 什么是跨域?

    当一个请求 url 的协议、域名、端口三者之间任意一个与当前页面 url 不同即为跨域

    请求页面url 当前页面url 是否跨域 原因
    http://www.test.com/ http://www.test.com/index.html 同源(协议、域名、端口号相同)
    http://www.test.com/ https://www.test.com/index.html 跨域 协议不同(http/https)
    http://www.test.com/ http://www.baidu.com/ 跨域 主域名不同(test/baidu)
    http://www.test.com/ http://blog.test.com/ 跨域 子域名不同(www/blog)
    http://www.test.com:8080/ http://www.test.com:7001/ 跨域 端口号不同(8080/7001)

    1.3 非同源限制

    【1】无法读取非同源网页的 CookieLocalStorageIndexedDB

    【2】无法接触非同源网页的 DOM

    【3】无法向非同源地址发送 AJAX 请求

    二、案例

    假设我们是前后段分离的项目,分别部署在以下两个ip上

    前端页面的地址为 http://127.0.0.1:8848/test/index.html

    后台服务的地址为 http://99.48.59.195:8082/

    前后端的主要代码如下所示:

    后端接口 HelloController.java

    import com.example.security.entity.User;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Map;
    
    @RestController
    public class HelloController {
    
        @GetMapping("/testGet")
        public String testGet(String username) {
            return username;
        }
    
        @GetMapping("/testGet2")
        public String testGet2(String username, String password) {
            return username + "," + password;
        }
    
        @PostMapping("/testPost")
        public Map testPost(@RequestBody Map<String, Object> map) {
            return map;
        }
        
        @PostMapping("/testPost2")
        public User testPost2(User user) {
            return user;
        }
    }
    HelloClass.java

    前端页面 index.html

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <link type="test/css" href="css/style.css" rel="stylesheet">
    
        <body>
    
            <input type="text" style=" 220px;" id="urlText" value="http://99.48.59.195:8082/testGet" />
            <input type="button" id="cors" value="testGet" /><br />
            <input type="text" style=" 220px;" id="urlText1" value="http://99.48.59.195:8082/testGet2" />
            <input type="button" id="cors1" value="testGet2" /><br />
            <input type="text" style=" 220px;" id="urlText2" value="http://99.48.59.195:8082/testPost" />
            <input type="button" id="cors2" value="testPost" /><br />
            <input type="text" style=" 220px;" id="urlText3" value="http://99.48.59.195:8082/testPost2" />
            <input type="button" id="cors3" value="testPost2" />
            <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
            <script type="text/javascript">
                $(function() {
                    $("#cors").click(
                        function() {
                            var url2 = $("#urlText").val();
                            $.get({
                                url: url2,
                                data: "username=jack",
                                success: function(data) {
                                    alert("username is " + data);
                                }
                            })
                        });
                    $("#cors1").click(
                        function() {
                            var url2 = $("#urlText1").val();
                            $.get(url2, {
                                    username: "John",
                                    password: "2pm"
                                },
                                function(data) {
                                    alert("Data Loaded: " + data);
                                });
                        });
                    $("#cors2").click(
                        function() {
                            var url2 = $("#urlText2").val();
                            $.post({
                                dataType: 'application/json',
                                contentType: 'application/json',
                                url: url2,
                                data: JSON.stringify({
                                    username: "John",
                                    password: "2pm"
                                }),
                                // 指定dataType为json时可能不能执行success回调,可参考https://blog.csdn.net/zls986992484/article/details/51404429
                                success: function(data) {
                                    console.log(11);
                                    alert("success");
                                }
                            })
                        });
    
                    // 这种方式参数为formDate格式
                    $('#cors3').click(function() {
                        var url2 = $("#urlText3").val();
                        $.post(
                            url2, {
                                username: 'admin',
                                password: '123'
                            },
                            function(result) {
                                alert("success");
                            }, "json"
                        );
                    });
                });
            </script>
        </body>
    </html>
    index.html

    直接调用接口时,根据浏览器的同源策略可以知道如果我们此时不进行跨域处理的话,访问后端地址是会失败的,控制台会打印如下错误信息

    三、解决方案

    3.1 实现WebMvcConfigurer,重写跨域处理方法

    添加 CORS 的配置信息,我们创建一个 CORSConfiguration 配置类重写如下方法,如下所示:

    WebMvcConfigurer.java

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * 这里我们的CORSConfiguration配置类继承了WebMvcConfigurer父类并且重写了addCorsMappings方法,我们来简单介绍下我们的配置信息
     * allowedOrigins:允许设置的请求域名访问我们的跨域资源,可以固定单条或者多条内容,如:"http://www.baidu.com",只有百度可以访问我们的跨域资源。
     * addMapping:配置可以被跨域的路径,可以任意配置,可以具体到直接请求路径。
     * allowedMethods:设置允许的请求方法类型访问该跨域资源服务器,如:POST、GET、PUT、OPTIONS、DELETE等。
     * allowedHeaders:允许所有的请求header访问,可以自定义设置任意请求头信息,如:"X-YYYY-TOKEN"
     * allowCredentials: 是否允许请求带有验证信息,用户是否可以发送、处理 cookie
     */
    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")//项目中的所有接口都支持跨域
                    .allowedOrigins("*")//所有地址都可以访问,也可以配置具体地址
                    .allowCredentials(true) //是否允许请求带有验证信息
                    .allowedMethods("*")//"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
                    .allowedHeaders("*").maxAge(3600);// 跨域允许时间
        }
    }
    

    3.2 使用过滤器

    方案一:

    配置如下过滤器 

    CorsFilter.java

    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Configuration
    public class CorsFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
            // 这里填写你允许进行跨域的主机ip,*表示所有(正式上线时可以动态配置具体允许的域名和IP)
            // response.setHeader("Access-Control-Allow-Origin", "*");
    
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            //获取来源网站
            String originStr = request.getHeader("Origin");
            //允许该网站进行跨域请求
            response.setHeader("Access-Control-Allow-Origin", originStr);
            // 允许的访问方法
            response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
            // Access-Control-Max-Age 用于 CORS 相关配置的缓存
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, client_id, uuid, Authorization");
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            //表示是否允许请求携带凭证信息,若要返回cookie、携带seesion等信息则将此项设置为true
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Pragma", "no-cache");
            filterChain.doFilter(servletRequest, response);
        }
    
        @Override
        public void destroy() {
        }
    }

    方案二:

    利用过滤器配置跨域还可以使用如下方法

    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    
    @Configuration
    public class CorsFilter {
        @Bean
        public FilterRegistrationBean<CorsFilter> corsFilter() {
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            //表示允许所有,可以设置需要的地址
            config.addAllowedOrigin("*");
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            //表示是否允许请求带有验证信息
            config.setAllowCredentials(true);
    
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            //CORS配置对所有接口都有效
            source.registerCorsConfiguration("/**", config);
            FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
            bean.setOrder(0);
            return bean;
        }
    
    }
    

    3.3 使用 @CrossOrigin 注解 

    import com.example.security.entity.User;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Map;
    
    /**
     * 代码说明:
     * @CrossOrigin这个注解可以用在方法上,也可以用在类上,用在类上时,表示该controller所有映射都支持跨域请求。
     * 如果不设置他的value属性,或者是origins属性,就默认是可以允许所有的URL/域访问。
     * value属性可以设置多个URL。
     * origins属性也可以设置多个URL。
     * maxAge属性指定了准备响应前的缓存持续的最大时间。就是探测请求的有效期。
     * allowCredentials属性表示用户是否可以发送、处理 cookie。默认为false
     * allowedHeaders 属性表示允许的请求头部有哪些。
     * methods 属性表示允许请求的方法,默认get,post,head。
     */
    
    //直接在Controller类上面添加/@CrossOrigin注解。表示该controller所有映射都支持跨域请求。
    //@CrossOrigin(origins = "http://127.0.0.1:8848", maxAge = 3600)
    @CrossOrigin
    @RestController
    public class HelloController {
    
        @GetMapping("/testGet")
        public String testGet(String username) {
            return username;
        }
    
        @GetMapping("/testGet2")
        public String testGet2(String username, String password) {
            return username + "," + password;
        }
    
        @PostMapping("/testPost")
        public Map testPost(@RequestBody Map<String, Object> map) {
            return map;
        }
    
        @PostMapping("/testPost2")
        public User testPost2(User user) {
            return user;
        }
    }
    

    3.4 nginx 转发请求处理跨域

    前面我们介绍过跨域产生的几种情况,只要保证同源(协议、域名、端口号相同),就不会出现跨域问题。

    我们现在前端页面服务器所在IP为 http://127.0.0.1:8848 

    需要调用的后台服务的地址为 http://99.48.59.195:8082/test/**

     那么我们可以在前端服务器的 nginx 配置文件中添加如下代理:

    server {
            listen       8084;
            server_name  localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    	location / {
            root   /usr/local/nginx/html;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            }
            location /test/ {
                    proxy_pass http://99.48.59.195:8082/test/;
                    proxy_read_timeout 150;
                        proxy_set_header Host $host;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header REMOTE-HOST $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
            # redirect server error pages to the static page /50x.html
            #
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    		}
    

    这段配置表示的当前端服务器调用 8084 端口的请求时,会自动将请求转发到 http://99.47.134.33:8090/  。对于前端请求来说此时的协议、域名、端口号都是相同的,那么就不会出现跨域问题。

    三、测试

    点击按钮调用接口,成功返回数据,说明我们这里成功进行了跨域处理。

    注意:

    1.如果项目带有登录功能,需要验证登录凭证cookie时,此时需要在跨域配置中设置 Access-Control-Allow-Credentials 属性

            //表示是否允许请求携带凭证,若要返回cookie、携带seesion等信息则将此项设置为true
            response.setHeader("Access-Control-Allow-Credentials", "true");

    否则会出现如下错误信息,这句话明确表明了此时要将 Access-Control-Allow-Credentials 头设置为 true

    The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'

    2.在使用过滤器方案一处理跨域时,如果使用了如下配置:

            // 这里填写你允许进行跨域的主机ip,*表示所有(正式上线时可以动态配置具体允许的域名和IP)
            response.setHeader("Access-Control-Allow-Origin", "*");
            //表示是否允许请求携带凭证信息,若要返回cookie、携带seesion等信息则将此项设置为true
            response.setHeader("Access-Control-Allow-Credentials", "true");
    

    这里表示请求需要携带凭证信息,允许所有 ip 进行跨域。理论上是没有问题的,但是在测试的时候会发现控制台会抛出如下错误信息:

    错误表明当请求的凭据模式为 “include” 时,响应中的标头不可以使用通配符 “*”。需要指定域名,这时我们可以对跨域配置作如下修改:

            HttpServletRequest request = (HttpServletRequest) servletRequest;
            //获取来源网站
            String originStr = request.getHeader("Origin");
            //允许该网站进行跨域请求
            response.setHeader("Access-Control-Allow-Origin", originStr);
            //表示是否允许请求携带凭证信息,若要返回cookie、携带seesion等信息则将此项设置为true
            response.setHeader("Access-Control-Allow-Credentials", "true");
    

    参考:什么是跨域?跨域解决方法

  • 相关阅读:
    IntrospectorCleanupListener作用
    买新车流程
    EXCEL-表格安全性:加密给与不同操作权限、表格怎么不让别人复制粘贴?
    全球安全帽品牌推荐整理
    Oracle-SQL语句的语法顺序和执行顺序
    Oracle-除了会排序,你对ORDER BY的用法可能一无所知!
    EXCEL——排序函数RANK,6种花式使用技巧
    常用云盘总结
    关于运算符结合顺叙的一些小探索
    类继承小总结
  • 原文地址:https://www.cnblogs.com/sueyyyy/p/10129575.html
Copyright © 2020-2023  润新知