• 还在问跨域?本文记录js跨域的多种实现实例


      前言

      众所周知,受浏览器同源策略的影响,产生了跨域问题,那么我们应该如何实现跨域呢?本文记录几种跨域的简单实现

      前期准备

      为了方便测试,我们启动两个服务,10086(就是在这篇博客自动生成的项目,请戳:SpringBoot系列——Spring-Data-JPA(究极进化版) 自动生成单表基础增、删、改、查接口)服务提供者,10087服务消费者,消费者有一个页面test.html跟一个后端controller

    <!DOCTYPE html>
    <!--解决idea thymeleaf 表达式模板报红波浪线-->
    <!--suppress ALL -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <!-- 引入静态资源 -->
        <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
    </head>
    <body>
        <h3>直接ajax请求10086服务的接口</h3>
        <button id="button">发送ajax请求</button>
        <br/>
        <textarea id="text" style=" 380px; height: 380px;">
    
        </textarea>
    </body>
    <script th:inline="javascript">
        ctx = [[${#request.getContextPath()}]];//应用路径
    
        //绑定按钮点击事件
        $("body").on("click","#button",function(e){
            $("#text").text("");
    //发送ajax请求 $.get("http://localhost:10086/tbUser/get/1",function (data) { $("#text").text(data); }) }) </script> </html>
        @RequestMapping("page/test")
        public ModelAndView pageLogin() {
            return new ModelAndView("test.html");
        }

      我们先启动10086服务

      浏览器访问接口,正常获取数据

      启动10087服务

      访问test.html页面,直接ajax请求10086服务的接口,直接报错

       几种跨域方式

      1、后端安全跨域

      前端发起请求后端,后端使用httpclient(使用方法参考:httpclient+jsoup实现小说线上采集阅读)或者feign(使用方法参考:SpringCloud系列——Feign 服务调用)安全跨域

      我们给10087服务消费者新增两个controller接口,用于后台调用10086跟响应数据,并修改test.html

      httpclient

    <!DOCTYPE html>
    <!--解决idea thymeleaf 表达式模板报红波浪线-->
    <!--suppress ALL -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <!-- 引入静态资源 -->
        <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
    </head>
    <body>
        <h3>后端安全跨域(httpclient)</h3>
        <button id="button">发送ajax请求</button>
        <br/>
        <textarea id="text" style=" 380px; height: 380px;">
    
        </textarea>
    </body>
    <script th:inline="javascript">
        ctx = [[${#request.getContextPath()}]];//应用路径
        //绑定按钮点击事件
        $("body").on("click","#button",function(e){
            $("#text").text("");
            //发送ajax请求
            $.get(ctx + "/test/httpclient",{id:"1"},function (data) {
                $("#text").text(data);
            })
        })
    </script>
    </html>
        @RequestMapping("/test/httpclient")
        public Object httpclient(String id) {
            String result = null;
            try {
                //创建httpclient对象 (这里设置成全局变量,相对于同一个请求session、cookie会跟着携带过去)
                CloseableHttpClient httpClient = HttpClients.createDefault();
                //创建get方式请求对象
                HttpGet httpGet = new HttpGet("http://localhost:10086/tbUser/get/"+id);
                httpGet.addHeader("Content-type", "application/json");
                //包装一下
                httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
                httpGet.addHeader("Connection", "keep-alive");
    
                //通过请求对象获取响应对象
                CloseableHttpResponse response = httpClient.execute(httpGet);
                //获取结果实体
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    result = EntityUtils.toString(response.getEntity(), "GBK");
                }
    
                //释放链接
                response.close();
            }
            //这里还可以捕获超时异常,重新连接抓取
            catch (Exception e) {
                result = null;
                e.printStackTrace();
            }
            return result;
        }

      feign

      我们先在10087服务消费者修改test.html,新增controller接口,maven引入feign,创建TestFeign,值得注意的是消费者的返回值必须与提供者的返回值一致,参数对象也要一致

    <!DOCTYPE html>
    <!--解决idea thymeleaf 表达式模板报红波浪线-->
    <!--suppress ALL -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <!-- 引入静态资源 -->
        <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
    </head>
    <body>
        <h3>后端安全跨域(feign)</h3>
        <button id="button">发送ajax请求</button>
        <br/>
        <textarea id="text" style=" 380px; height: 380px;">
    
        </textarea>
    </body>
    <script th:inline="javascript">
        ctx = [[${#request.getContextPath()}]];//应用路径
        //绑定按钮点击事件
        $("body").on("click","#button",function(e){
            $("#text").text("");
            //发送ajax请求
            $.get(ctx + "/test/feign",{id:"1"},function (data) {
                $("#text").text(data);
            })
        })
    </script>
    </html>
    @FeignClient(name = "http://localhost:10086", path = "/tbUser/")
    public interface TestFeign {
        @RequestMapping(value = "get/{id}")
        Result<UserVo> get(@PathVariable("id") Integer id);
    }
        @Autowired
        private TestFeign testFeign;
    
        @RequestMapping("/test/feign")
        public Object feign(Integer id){
            return testFeign.get(id);
        }

      直接使用url会报这个错,因为我们没有注册这两个服务,eureka也没起...,所以想使用feign调用,两个服务都需要在eureka上注册,负载均衡器才能找到它

      我们将两个服务注册到eureka上(参考springcloud系列博客之SpringCloud系列——Eureka 服务注册与发现),将@FeignClient(name = "http://localhost:10086", path = "/tbUser/")的name的值改成10086服务提供者的 spring.application.name的值即可

      2、jsonp

       由于同源策略,浏览器禁止向不同源的服务器发起请求,但是 HTML 的<script> 元素是一个例外,我们可以利用标签的src发起请求,服务提供者的后端响应消费者期望的数据格式(必须是符合js对象的格式,否则会在回调函数解析的时候报错)

      我们先重写10086服务提供者的get接口,使其返回值符合消费者期待的格式

    @RestController
    @RequestMapping("/tbUser/")
    public class TbUserController extends CommonController<TbUserVo, TbUser, Integer> {
        @Autowired
        private TbUserService tbUserService;
    
        @RequestMapping("get")
        public Object get(Integer id, String callback) {
            Result<TbUserVo> result = super.get(id);
            TbUserVo tbUserVo = result.getData();
            StringBuffer re = new StringBuffer();
            re.append("{");
            re.append("'flag':'" +  result.isFlag() + "',");
            re.append("'msg':'" +  result.getMsg() + "',");
            re.append("'data':{");
            re.append("'id':'" +  tbUserVo.getId() + "',");
            re.append("'username':'" +  tbUserVo.getUsername() + "',");
            re.append("'password':'" +  tbUserVo.getPassword() + "',");
            re.append("'created':'" +  tbUserVo.getCreated() + "',");
            re.append("'descriptionId':'" +  tbUserVo.getDescriptionId()+"'");
            re.append("}}");
            return callback + "(" + re.toString() + ")";
        }
    }

      修改10087服务消费者的test.html

    <!DOCTYPE html>
    <!--解决idea thymeleaf 表达式模板报红波浪线-->
    <!--suppress ALL -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <!-- 引入静态资源 -->
        <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
    </head>
    <body>
        <h3>jsonp</h3>
        <button id="button">发送ajax请求</button>
        <br/>
        <textarea id="text" style=" 380px; height: 380px;">
    
        </textarea>
    </body>
    <script th:inline="javascript">
        ctx = [[${#request.getContextPath()}]];//应用路径
        //绑定按钮点击事件
        $("body").on("click","#button",function(e){
            //构造一个<srcipt>标签
            $("body").append("<script id='temporaryScript'  src="http://localhost:10086/tbUser/get?id=1&callback=callback"></script>");
        })
    
        //回调
        function callback(data) {
            $("#text").text("");
            $("#text").text(JSON.stringify(data));
            //过河拆桥
            $("#temporaryScript").remove();
        }
    </script>
    </html>

      


      3、cors

      详情介绍可以看这里:CORS通信 ,代码实现过程参考:Java实现CORS跨域请求 

      参考大佬博客,我们在10086服务提供者新建一个CorsFilter过滤器,

    @Component
    @ServletComponentScan
    @WebFilter(filterName = "corsFilter", //过滤器名称
            urlPatterns = "/tbUser/*",//拦截路径
            initParams = {@WebInitParam(name = "allowOrigin", value = "http://localhost:10087"),//允许来源
                    @WebInitParam(name = "allowMethods", value = "GET,POST,PUT,DELETE,OPTIONS"),//允许请求方法
                    @WebInitParam(name = "allowCredentials", value = "true"),
                    @WebInitParam(name = "allowHeaders", value = "Content-Type,X-Token")})
    public class CorsFilter implements Filter {
    
        private String allowOrigin;
        private String allowMethods;
        private String allowCredentials;
        private String allowHeaders;
        private String exposeHeaders;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            allowOrigin = filterConfig.getInitParameter("allowOrigin");
            allowMethods = filterConfig.getInitParameter("allowMethods");
            allowCredentials = filterConfig.getInitParameter("allowCredentials");
            allowHeaders = filterConfig.getInitParameter("allowHeaders");
            exposeHeaders = filterConfig.getInitParameter("exposeHeaders");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            if (!StringUtils.isEmpty(allowOrigin)) {
                if(allowOrigin.equals("*")){
                    response.setHeader("Access-Control-Allow-Origin", allowOrigin);
                }else{
                    List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
                    if (allowOriginList != null && allowOriginList.size() > 0) {
                        String currentOrigin = request.getHeader("Origin");
                        if (allowOriginList.contains(currentOrigin)) {
                            response.setHeader("Access-Control-Allow-Origin", currentOrigin);
                        }
                    }
                }
            }
            if (!StringUtils.isEmpty(allowMethods)) {
                response.setHeader("Access-Control-Allow-Methods", allowMethods);
            }
            if (!StringUtils.isEmpty(allowCredentials)) {
                response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
            }
            if (!StringUtils.isEmpty(allowHeaders)) {
                response.setHeader("Access-Control-Allow-Headers", allowHeaders);
            }
            if (!StringUtils.isEmpty(exposeHeaders)) {
                response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    }

      然后修改10087服务消费者调用方式,改成普通的ajax请求即可

    <!DOCTYPE html>
    <!--解决idea thymeleaf 表达式模板报红波浪线-->
    <!--suppress ALL -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <!-- 引入静态资源 -->
        <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
    </head>
    <body>
        <h3>cors</h3>
        <button id="button">发送ajax请求</button>
        <br/>
        <textarea id="text" style=" 380px; height: 380px;">
    
        </textarea>
    </body>
    <script th:inline="javascript">
        ctx = [[${#request.getContextPath()}]];//应用路径
        //绑定按钮点击事件
        $("body").on("click","#button",function(e){
            //发送ajax请求
            $.get("http://localhost:10086/tbUser/get/1",function (data) {
                $("#text").text("");
                $("#text").text(JSON.stringify(data));
            })
        })
    </script>
    </html>

      在启动10086服务提供者时出现一个启动报错,说corsFilter已经存在,叫我们改名或者启用覆盖,我们启用一下覆盖  PS:最好是重命名我们的bean

      启动成功后我们进行测试

      测试下list请求,修改这一部分

            //发送ajax请求
            $.post("http://localhost:10086/tbUser/list",{username:"张三"},function (data) {
                $("#text").text("");
                $("#text").text(JSON.stringify(data));
            })

      没有问题!

       以上是CORS是基于Filter过滤器的实现,事实上,springboot通过@CrossOrigin注解优雅的实现CORS跨域,我们在10086服务提供者的controller层那里加入@CrossOrigin注解:

    @RestController
    @RequestMapping("/tbUser/")
    @CrossOrigin(origins = "http://localhost:10087", methods = "GET,POST,PUT,DELETE,OPTIONS", allowedHeaders = "Content-Type,X-Token",allowCredentials = "true")
    public class TbUserController extends CommonController<TbUserVo, TbUser, Integer> {
        @Autowired
        private TbUserService tbUserService;
    
    }

      @CrossOrigin注解,官方文档:https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

      1、Spring 4.2之后提供了跨域注解 @CrossOrigin;

      2、可以用在方法或Controller上;

      3、Controller和方法上都有时,Spring会合并两个注解的属性一起使用;

      注解属性有以下7个:

    String[] value() default {}
    String[] origins() default {} //允许来源
    String[] allowedHeaders() default {}
    String[] exposedHeaders() default {}
    RequestMethod[] methods() default {} //允许调用方法
    String  allowCredentials() default {}
    long maxAge() default -1L

      如果是SpringBoot项目,还有更简洁的配置,我们看一下官网介绍:CORS Support

    management.endpoints.web.cors.allowed-origins=http://localhost:10087
    management.endpoints.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS

      后记

       跨域暂时先记录到这里,如果大家发现有什么错误,还望指正

  • 相关阅读:
    笔试题-同线程Lock语句递归不会死锁
    EnterWriteLock与lock有啥区别?
    lock(this)其实是个坑
    实际项目中关于ManualResetEvent的用法
    以1个实例讲解ManualResetEvent的作用
    Session有什么重大BUG,微软提出了什么解决方案
    Session和Cookie实现购物车
    VMware打开虚拟机黑屏
    idea使用java整合ice
    elasticsearch基本概念
  • 原文地址:https://www.cnblogs.com/huanzi-qch/p/10497396.html
Copyright © 2020-2023  润新知