详解跨域及CORS解决方案

1.发现问题

在开发基于WEB应用的时候,一般情况下前端负责界面展示,后端负责业务逻辑,尤其是当前分布式、微服务、前后端分离架构的广泛使用,前端调用后端获取数据,难免会出现数据访问跨域现象:

当浏览器console出现下面的提示,说明出现了跨域问题。

Access to XMLHttpRequest at 'http://www.float.com/goods/findPage?page=1&size=5' from origin 'www.integer.net.cn' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

关键词:

origin:域、源

CORS:全称是"跨域资源共享"(Cross-Origin Resource Sharing)

blocked:阻止,阻塞

Access-Control-Allow-Origin:HTTP协议中与跨域有关的请求头信息。

CORS policy:CORS策略

接下来有个问题,域是什么鬼?

2.什么是域

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

3.为什么会出现跨域问题

出于安全考虑,浏览器使用同源策略(SameOriginPolicy)进行访问的限制。

所谓同源(或同一个域)就是两个请求具有相同的协议(protocol),主机(host)和端口号(port),这三者均相同则表示这两个请求同源。

同源策略防止当前域的javascript脚本和另外一个域的内容进行交互,这就是为什么出现跨域访问被阻止的原因。

同源策略是Web应用程序安全模型中的一个重要概念,它由 Netscape 公司在1995年引入浏览器。该策略最初是为保护DOM的访问而设计的,Web浏览器允许第一个Web页面中包含的脚本访问第二个Web页面中的数据,但前提是两个Web页面具有相同的源(origin)。后来进行了扩展,此策略可防止一个页面上的恶意脚本(ajax请求)访问另一个网页上的敏感数据。

恶意脚本访问敏感数据

目前Web应用程序广泛依赖HTTP cookie来维护经过身份验证的用户会话,服务器基于cookie信息来显示或者获取敏感信息。客户端必须保证无关站点与当前站点严格分离,以防止数据机密性或完整性丢失。

同源策略主要防止一个域的javascript脚本和另外一个域的内容进行交互。

主要限制包括:

不同源的页面之间Cookie、LocalStorage 和 IndexDB无法共享

不同源AJAX 请求不能发送

通过相应的HTML标记嵌入的资源不受限制,如图像,CSS和脚本

图像:

CSS:

脚本:

(1)当访问http://www.integer.net.cn的时候,浏览器发送GET 请求获取主页,这时候当前页面的域指定为http://www.integer.net.cn

(2)通过访问1.png图片,没有跨域,允许访问,通过访问2.png图片,虽然跨域,但访问的是HTML嵌入资源,不受同源策略影响,依然可以访问。

(3)通过访问a.css,没有跨域,允许访问,通过< link href="http://www.double.net.cn/b.css">访问b.css,虽然跨域,但访问的是HTML嵌入资源,不受同源策略影响。

(4)使用ajax异步请求http://www.integer.net.cn域下的数据,没有跨域,允许访问

(5)使用ajax异步请求http://www.float.com域下的数据,此时跨域,同源策略阻止继续访问,是否允许访问由CORS策略控制。

从以上分析可以看出,只有当ajax异步请求跨域时,同源策略才会起作用,跨域请求就一定不能访问呢?当然不是,上面已经提及,是否能访问跨域资源由CORS策略决定的,那么什么是CORS策略呢?

4.CORS跨域解决方案(服务端)

CORS策略,它允许浏览器向跨域服务器发出XMLHttpRequest(AJAX)请求,从而克服了AJAX只能同源访问的限制。

整个CORS通信过程,前端都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息(针对简单请求),有时还会多出一次附加的请求(针对复杂请求),但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。

上面这段话,翻译成大白话就是浏览器发现AJAX请求跨域,添加附加信息(简单请求)或者附加请求(复杂请求)发送到服务器,如果服务器端允许当前访问,则继续访问返回响应结果,如果服务器不允许该来源访问,则会出现跨域访问现象。所以,主动权掌握在服务器手里。这就是CORS策略。

就像你想要约隔壁班的女生看电影,人家和你不是一个班根本不认识你,去不去由人家决定。当然,附加的信息就是你表明自己身份去邀请人家,这里还有一个问题,如果只是看电影这种简单请求,你拿着票表达诚意就行。但是对于其他复杂请求,问一次肯定不行,先要进行一次预请求,试探一下,试探成功后再进行复杂请求。

那么哪些请求是简单请求,哪些是复杂请求呢?

简单请求需要满足以下几点:

请求是GET、POST、HEAD三种

HTTP的头信息不超出以下几种字段 Accept、Accept-Language、Content-Language、Content-Type

Content-Type 的值仅限于下列三者之一:text/plain、multipart/form-data、application/x-www-form-urlencoded

什么是复杂请求呢,除了简单请求都是复杂请求。

比如说你需要发送PUT、DELETE等HTTP请求,或者发送Content-Type: application/json的内容,这些就都是复杂请求。

那么简单请求中附加的信息是什么呢?

跨域请求HTTP头当中要求包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,开发者不需要操作,这就是简单请求附加的信息。

服务器端对简单请求进行响应,部分响应头及解释如下:

Access-Control-Allow-Origin(必含) - 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"。

Access-Control-Allow-Credentials(可选) - 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest对象(ajax请求)当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则导致请求失败。

简单请求

任何不满足上述简单请求要求的请求,都属于复杂请求。

复杂请求会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。预检请求为OPTIONS请求,用于向服务器请求权限信息的。预检请求被成功响应后,才会发出真实请求,携带真实数据。

复杂请求

预请求以OPTIONS形式发送,当中同样包含域,并且还包含了两项CORS特有的内容:

Access-Control-Request-Method – 该项内容是实际请求的种类,可以是GET、POST之类的简单请求,也可以是PUT、DELETE等等。

Access-Control-Request-Headers – 该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部。

显而易见,这个预请求实际上就是在为之后的实际请求发送一个权限请求,在预回应返回的内容当中,服务端应当对这两项进行回复,以让浏览器确定请求是否能够成功完成。

复杂请求的部分响应头及解释如下:

Access-Control-Allow-Origin(必含) – 和简单请求一样的,必须包含一个域。

Access-Control-Allow-Methods(必含) – 这是对预请求当中Access-Control-Request-Method的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。

Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。这里在实际使用中有遇到,所有支持的头部一时可能不能完全写出来,而又不想在这一层做过多的判断,没关系,事实上通过request的header可以直接取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Allow-Headers即可。

Access-Control-Allow-Credentials(可选) – 和简单请求当中作用相同。

Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存。

一旦预回应如期而至,所请求的权限也都已满足,则实际请求开始发送。

5.Spring CORS –@CrossOrigin注解

Spring MVC 提供@CrossOrigin 注解. 该注解可以标注在类或者方法上

5.1. Spring CORS 配置

@CrossOrigin 默认将允许所有的源,所有头信息,@RequestMapping标注的方法进行跨域访问,maxAge 为30分钟。

CrossOrigin 配置参数

5.2. @CrossOrigin 注解标注在Controller类

@CrossOrigin(origins ="*", allowedHeaders ="*")

@Controller

publicclassHomeController{

    @GetMapping(path="/")

    public String homeInit(Model model) {

        return"home";

    }

}

表明该类的所有方法,均实现CORS策略。

5.3. @CrossOrigin 注解标注在方法

@Controller

publicclassHomeController{

    @CrossOrigin(origins ="*", allowedHeaders ="*")

    @GetMapping(path="/")

    public String homeInit(Model model) {

        return"home";

    }

}

表明该方法实现CORS策略

5.4. @CrossOrigin 在方法级覆盖类级注解

@Controller

@CrossOrigin(origins ="*", allowedHeaders ="*")

public class HomeController{

    @CrossOrigin(origins ="http://example.com")

    @GetMapping(path="/")

    publicString homeInit(Model model) {

        return"home";

    }

}

homeInit() 方法只能被域 http://example.com访问,HomeController 的其他方法可以被所有的域

6.Spring CORS – 全局配置

6.1. 实现WebMvcConfigurer

为了让整个应该所有的请求均实现CORS跨域方案, 可以使用WebMvcConfigurer 并添加到 CorsRegistry中。

import org.springframework.context.annotation.Configuration;

import rg.springframework.web.servlet.config.annotation.CorsRegistry;

import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration

@EnableWebMvc

publicclass CorsConfiguration implements WebMvcConfigurer{

@Override

public void addCorsMappings(CorsRegistry registry) {

            registry.addMapping("/**").allowedMethods("GET", "POST");

    }

}

6.2. WebMvcConfigurer Bean

spring boot应用中, 推荐声明一个 WebMvcConfigurer bean.

@Configuration

publicclassCorsConfiguration{

@Bean

publicWebMvcConfigurercorsConfigurer() {

        return new WebMvcConfigurer() {

        @Override

        public void addCorsMappings(CorsRegistry registry){

               registry.addMapping("/**").allowedOrigins("http://localhost:8080");

    }       

 };    

}

}

6.3 CORS 与Spring Security

为了能够使CORS 支持 Spring security, 配置CorsConfigurationSource bean 并且使用 HttpSecurity.cors() 进行设置。

@EnableWebSecurity

publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{

@Override

protected void configure(HttpSecurity http)throwsException{

        http.cors().and()//other config

    }

    @Bean

    CorsConfigurationSource corsConfigurationSource() {

        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));

        configuration.setAllowedMethods(Arrays.asList("GET","POST"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", configuration);

        return source;

    }

}

7.Java CORS过滤器

如果应用中没有集成Spring或者SpringBoot框架,可以直接使用过滤器来进行配置,基本原理就是对Http 头信息就行设置。

7.1Response Headers

· Access-Control-Allow-Origin : 指示允许哪些域进行跨域访问. “*” 代表没有限制.

· Access-Control-Allow-Credentials : 指示跨域访问是否支持用户身份,即cookie.

· Access-Control-Expose-Headers : 指示哪些头信息暴露出来.

· Access-Control-Max-Age : 指示预请求响应在客户端缓存的时间.

· Access-Control-Allow-Methods : 指示允许哪些方法可以访问资源

· Access-Control-Allow-Headers :指示在实际请求中哪些头信息被允许.

7.2. Request Headers

· Origin : 指示跨域请求或者预请求的来源.

· Access-Control-Request-Method : 在预请求中使用,告诉服务器实际请求讲使用该方法

· Access-Control-Request-Headers :在预请求中使用,告诉服务器实际请求中将带的头信息.

7.3. Java CORS 过滤器demo

import java.io.IOException;

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.annotation.WebFilter;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* Servlet Filter implementation class CORSFilter

*/

// Enable it for Servlet 3.x implementations

/* @ WebFilter(asyncSupported = true, urlPatterns = { "/*" }) */

public class CORSFilter implements Filter {

    /**

    * Default constructor.

    */

    public CORSFilter() {

        // TODO Auto-generated constructor stub

    }

    /**

    * @see Filter#destroy()

    */

    public void destroy() {

        // TODO Auto-generated method stub

    }

    /**

    * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)

    */

    public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain chain)

            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;

        System.out.println("CORSFilter HTTP Request: " + request.getMethod());

        // Authorize (allow) all domains to consume the content

        ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", "*");

        ((HttpServletResponse) servletResponse)

                    .addHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST");

        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        // For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshake

        if (request.getMethod().equals("OPTIONS")) {

            resp.setStatus(HttpServletResponse.SC_ACCEPTED);

            return;

        }

        // pass the request along the filter chain

        chain.doFilter(request, servletResponse);

    }

    /**

    * @see Filter#init(FilterConfig)

    */

    public void init(FilterConfig fConfig) throws ServletException {

        // TODO Auto-generated method stub

    }

}

web.xml配置

    CorsFilter

    com.howtodoinjava.examples.cors.CORSFilter

    CorsFilter

    /*

你可能感兴趣的:(详解跨域及CORS解决方案)