美文网首页WEB前端程序开发程序员
请求跨域和CORS协议详解

请求跨域和CORS协议详解

作者: 灬寒江天外丶 | 来源:发表于2017-03-01 11:39 被阅读0次

    什么是跨域

    简单的说即为浏览器限制访问A站点下的js代码对B站点下的url进行ajax请求。比如说,前端域名是www.abc.com,那么在当前环境中运行的js代码,出于安全考虑,访问www.xyz.com域名下的资源,是受到限制的。现代浏览器默认都会基于安全原因而阻止跨域的ajax请求,这是现代浏览器中必备的功能,但是往往给开发带来不便。特别是对我这样后台开发人员来讲,这个事情简直神奇。
    但跨域的需求却一直都在,为了跨域,勤劳勇敢的程序猿们想出了许许多多的方法,例如,jsonP、代理文件等等。但这些做法增加了许多不必要的维护成本,而且应用场景也有许多限制,例如jsonP并非XHR,所以jsonP只能使用GET传递参数。更详细的资料可以看这里 Web应用跨域访问解决方案汇总

    CORS协议

    如今的JS大有一统天下的趋势,浏览器已经成了大多应用最好的安身之所。哪怕在移动端也有各种Hybird方案,在本地文件系统的Web页面,也有需要获取外部数据的需求,而这些需求也必然是跨域的。在寻找跨域解决方案时,发现了最优雅解决方案就是HTML5来带了的“Cross-Origin Resource Sharing”的新特性,来赋予开发者权力决定资源是否允许被跨域访问。CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

    为什么说它优雅呢?
    整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。看看文档也不是什么难事嘛,就是需要在http头中设置Access-Control-Allow-Origin来决定需要允许哪些站点来访问。关于CROS协议更详细内容参考 跨域资源共享 CORS 详解

    CORS常见header

    CORS具有以下常见的header的例子:
    Access-Control-Allow-Origin: http://kbiao.me
    Access-Control-Max-Age: 3628800
    Access-Control-Allow-Methods: GET,PUT, DELETE
    Access-Control-Allow-Headers: content-type

    • “Access-Control-Allow-Origin”表明它允许”http://kbiao.me “发起跨域请求
    • “Access-Control-Max-Age”表明在3628800秒内,不需要再发送 预检验 请求,可以缓存该结果(上面的资料上我们知道CROS协议中,一个AJAX请求被分成了第一步的 OPTION预检测请求和正式请求)。
    • “Access-Control-Allow-Methods”表明它允许GET、PUT、DELETE的外域请求。
    • “Access-Control-Allow-Headers”表明它允许跨域请求包含content-type头
      当然在处理这个问题的时候前端也有不少坑,特别是Chrome浏览器不允许localhost的跨域请求,详细方案见我项目中负责前端解决方案的小伙伴。 假装有链接。

    常规解决方案

    知道了问题的原因,也知道了配套的解决办法,现在就让我们来实现解决。思路很简单,当前端要请求跨域资源时候,我们给它加上响应的响应头即可。很显然我们自己定义一个过滤器是最简单不过了。

    @Component
    public class myCORSFilter 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;
            String origin = (String) servletRequest.getRemoteHost()+":"+servletRequest.getRemotePort();
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");
            response.setHeader("Access-Control-Allow-Credentials","true");
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    @Component 是Spring的注解,关键部分在doFilter中,添加了我们需要的头, option是预检测需要所以需要允许, Authorization是做了oauth2登录响应所必须的, Access-Control-Allow-Credentials表示允许cookies。都是根据自己项目的实际需要配置。
    再配置Web.xml使得过滤器生效

    <filter>
      <filter-name>cors</filter-name>
      <filter-class>·CLASS_PATH·.myeCORSFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>cors</filter-name>
      <url-pattern>/api/*</url-pattern>
    </filter-mapping>
    

    接下来前端就可以像往常一样使用AJAX请求获得资源了,完全不需要做出什么改变。

    使用Spring的解决方案

    Sping 4中更优雅的办法,Spring从4.2开始提供了非常方便的实现跨域的方法,对CORS提供了完整支持。详细信息可以参考Spring官网开发者博客:cors-support-in-spring-framework

    在Controller中配置

    下面举例在方法和Controller上使用该注解:

    @CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
    @RestController
    @RequestMapping("/account")
    public class AccountController {
    
        @RequestMapping("/{id}")
        public Account retrieve(@PathVariable Long id) {
            // ...
        }
    
        @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
        public void remove(@PathVariable Long id) {
            // ...
        }
    }
    

    这里指定当前的AccountController中所有的方法可以处理domain2.com域上的请求。同样我们还可以在Controller中的每个方法上配置,像下面这样:

    @CrossOrigin(maxAge = 3600)
    @RestController
    @RequestMapping("/account")
    public class AccountController {
    
        @CrossOrigin("http://domain2.com")
        @RequestMapping("/{id}")
        public Account retrieve(@PathVariable Long id) {
            // ...
        }
    
        @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
        public void remove(@PathVariable Long id) {
            // ...
        }
    }
    

    在这个例子中,AccountController类上也有@CrossOrigin注解,retrieve方法上也有注解,Spring会合并两个注解的属性一起使用。

    全局配置

    全局配置默认会用于所有Controller中,定义类继承WebMvcConfigurerAdapter,设置跨域相关的配置:

    public class CorsConfigurerAdapter extends WebMvcConfigurerAdapter{
      
        @Override
        public void addCorsMappings(CorsRegistry registry) {
        
            registry.addMapping("/api/*").allowedOrigins("*")
                 .allowedMethods("GET", "PUT", "DELETE", "POST");
        }
    }
    

    将该类注入到容器中:

    <bean class="com.tmall.wireless.angel.web.config.CorsConfigurerAdapter"></bean>
    

    Spring Boot中的用法

    在Spring Boot中集成了Spring所有原生功能外,还提供了一个非常简单的外部配置方式即通过Spring Boot的全局配置文件application.properties来配置全局CORS生效:

    endpoints.cors.allowed-origins=http://www.xxx.com
    endpoints.cors.allowed-methods=GET,POST,PUT,DELETE
    

    也可以在入口函数上添加@CrossOrigin注解来实现跨域:

    @SpringBootApplication
    @CrossOrigin(origins = { "http://www.xxx.com" }, methods = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.POST,
            RequestMethod.DELETE })
    public class ConsumerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApplication.class, args);
        }
    }
    

    更详细的内容参考Spring 官方的hello world案例 :
    Enabling Cross Origin Requests for a RESTful Web Service

    相关文章

      网友评论

        本文标题:请求跨域和CORS协议详解

        本文链接:https://www.haomeiwen.com/subject/gjyqgttx.html