在开发基于WEB应用的时候,一般情况下前端负责界面展示,后端负责业务逻辑,尤其是当前分布式、微服务、前后端分离架构的广泛使用,前端调用后端获取数据,难免会出现数据访问跨域现象:
当浏览器console出现下面的提示,说明出现了跨域问题。
Access to XMLHttpRequest at 'http://localhost:8080/add' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
从上面的错误信息,能够看出来几个关键词Origin、CORS、policy等等,这些词究竟是什么含义呢?
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为不同的域。
出于安全考虑,**浏览器**使用同源策略(SameOriginPolicy)进行访问的限制。
所谓同源(或同一个域)就是两个请求具有相同的协议(protocol),主机(host)和端口号(port),这三者均相同则表示这两个请求同源。
同源策略防止当前域的javascript脚本和另外一个域的内容进行交互,这就是为什么出现跨域访问被阻止的原因。
同源策略是Web应用程序安全模型中的一个重要概念,它由 Netscape 公司在1995年引入浏览器。该策略最初是为保护DOM的访问而设计的,Web浏览器允许第一个Web页面中包含的脚本访问第二个Web页面中的数据,但前提是两个Web页面具有相同的源(origin)。后来进行了扩展,此策略可防止一个页面上的恶意脚本(ajax请求)访问另一个网页上的敏感数据。
目前Web应用程序广泛依赖HTTP cookie来维护经过身份验证的用户会话,服务器基于cookie信息来显示或者获取敏感信息。客户端必须保证无关站点与当前站点严格分离,以防止数据机密性或完整性丢失。
同源策略主要防止一个域的javascript脚本和另外一个域的内容进行交互。
主要限制包括:
- 不同源的页面之间Cookie、LocalStorage 和 IndexDB无法共享
- 不同源AJAX 请求不能发送
(1)当访问http://www.integer.net.cn的时候,浏览器发送GET 请求获取主页,这时候当前页面的域指定为http://www.integer.net.cn
(2)通过<img src="http://www.integer.net.cn/1.png">访问1.png图片,没有跨域,允许访问,通过<img src="http://www.double.net.cn/2.png">访问2.png图片,虽然跨域,但访问的是HTML嵌入资源,不受同源策略影响,依然可以访问。
(3)通过<link href="http://www.integer.net.cn/a.css" />访问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策略呢?
CORS策略,它允许浏览器向跨域服务器发出XMLHttpRequest(AJAX)请求,从而克服了AJAX只能同源访问的限制。
整个CORS通信过程,前端都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息(针对简单请求),有时还会多出一次附加的请求(针对复杂请求),但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。
上面这段话,翻译成大白话就是浏览器发现AJAX请求跨域,添加附加信息(简单请求)或者附加请求(复杂请求)发送到服务器,如果服务器端允许当前访问,则继续访问返回响应结果,如果服务器不允许该来源访问,则会出现跨域访问现象。所以,主动权掌握在服务器手里。这就是CORS策略。
就像你想要约隔壁班的女生看电影,人家和你不是一个班根本不认识你,去不去由人家决定。当然,附加的信息就是你表明自己身份去邀请人家,这里还有一个问题,如果只是看电影这种简单请求,你拿着票表达诚意就行。但是对于其他复杂请求,问一次肯定不行,先要进行一次预请求,试探一下,试探成功后再进行复杂请求。
那么哪些请求是简单请求,哪些是复杂请求呢?
简单请求需要满足以下几点:
什么是复杂请求呢,除了简单请求都是复杂请求。
比如说你需要发送PUT、DELETE等HTTP请求,或者发送Content-Type: application/json的内容,这些就都是复杂请求。
那么简单请求中附加的信息是什么呢?
跨域请求HTTP头当中要求包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,开发者不需要操作,这就是简单请求附加的信息。
服务器端对简单请求进行响应,部分响应头及解释如下:
任何不满足上述简单请求要求的请求,都属于复杂请求。
复杂请求会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求(preflight)。预检请求为OPTIONS请求,用于向服务器请求权限信息的。预检请求被成功响应后,才会发出真实请求,携带真实数据。
预请求以OPTIONS形式发送,当中同样包含域,并且还包含了两项CORS特有的内容:
复杂请求的部分响应头及解释如下:
Spring MVC 提供@CrossOrigin 注解. 该注解可以标注在类或者方法上
@CrossOrigin 默认将允许所有的源,所有头信息,@RequestMapping标注的方法进行跨域访问,maxAge 为30分钟。
@CrossOrigin(origins = "*", allowedHeaders = "*")
@Controller
public class HomeController
{
@GetMapping(path="/")
public String homeInit(Model model) {
return "home";
}
}
表明该类的所有方法,均实现CORS策略。
@Controller
public class HomeController
{
@CrossOrigin(origins = "*", allowedHeaders = "*")
@GetMapping(path="/")
public String homeInit(Model model) {
return "home";
}
}
表明该方法实现CORS策略。
@Controller
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class HomeController
{
@CrossOrigin(origins = "http://example.com")
@GetMapping(path="/")
public String homeInit(Model model) {
return "home";
}
}
homeInit() 方法只能被域 http://example.com访问,HomeController 的其他方法可以被所有的域
为了让整个应该所有的请求均实现CORS跨域方案, 可以使用WebMvcConfigurer 并添加到 CorsRegistry中。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class CorsConfiguration implements WebMvcConfigurer
{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST");
}
}
spring boot应用中, 推荐声明一个 WebMvcConfigurer bean.
@Configuration
public class CorsConfiguration
{
@Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080");
}
};
}
}
为了能够使CORS 支持 Spring security, 配置CorsConfigurationSource bean 并且使用 HttpSecurity.cors() 进行设置。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
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;
}
}
如果应用中没有集成Spring或者SpringBoot框架,可以直接使用过滤器来进行配置,基本原理就是对Http 头信息就行设置。
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 :指示在实际请求中哪些头信息被允许.
Origin : 指示跨域请求或者预请求的来源.
Access-Control-Request-Method : 在预请求中使用,告诉服务器实际请求讲使用该方法
Access-Control-Request-Headers :在预请求中使用,告诉服务器实际请求中将带的头信息.
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配置
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.howtodoinjava.examples.cors.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
本篇文章抄袭于以下地址:
文章作者**泰哥**是我的好朋友,因为他的这篇文章写的太好了,我承认我是无法超过这篇文章,所以只能是剽窃了。希望大家也关注泰哥的文章,为他点赞。