跨域定义
跨站 HTTP 请求(Cross-site HTTP request)是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。跨站 HTTP正常请求,但是结果被浏览器拦截了,就是跨域问题。
跨域问题只有在浏览器才会出现,javascript等脚本的主动http请求才会出现跨域问题。后端获取http数据不会存在跨域问题。
怎么才算跨域?
那我我们要先理解何为同源(同域)。
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。不同源就是跨域。
下表给出了相对http://store.company.com/dir/page.html跨域检测的示例:
链接 | 跨域与否 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 否 | |
http://store.company.com/dir/inner/another.html | 否 | |
https://store.company.com/secure.html | 是 | 不同协议 ( https和http ) |
http://store.company.com:81/dir/etc.html | 是 | 不同端口 ( 81和80) |
http://news.company.com/dir/other.html | 是 | 不同域名 ( news和store ) |
跨域情况总结如下:
- 不同协议 (https和http)
- 不同端口 (80和81)
- 不同域名 (news和store)
注:IE会有点不一样,更加宽松,但是前端兼容是短柄,所以不用理会IE。
为什么要有跨域限制?
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
还是安全问题,如果不限制,那么CSRF(Cross-site request forgery,中文名称:跨站请求伪造)攻击就很容易实现了。
举个例子,假设没有跨域限制:
假设你是a.com
网站的管理员,你在a.com
网站有一个权限是删除用户,比如说这个过程只需用你的身份登陆并且POST数据到http://a.com/deleteUser
,就可以实现删除操作。
然后假设有个b.com
网站被攻击了,别人种下了恶意代码,你点开的时候就会模拟跨域请求(你同时在浏览器中不同标签打开了a.com
和b.com
网站,并且你已经登录了a.com
),那么b.com
的恶意代码就可以模拟对a.com
的跨域请求,模拟一个用户删除操作是很简单的(当然是攻击者有意针对,而且知道了删除接口)。
通过例子可知道,这是多危险的事情,所以浏览器都会做跨域限制。
什么是预请求?
预请求
就是使用OPTIONS方
法。跨域请求首先需要发送预请求,即使用 OPTIONS
方法发起一个预请求
到服务器,以获知服务器是否允许该实际请求。预请求
的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
跨域才会有预请求,但是不是所有跨域请求都会发送预请求的。 预请求
服务器正常返回,浏览器还要判断是否合法,才会继续正常请求的。所以web服务程序需要针对options
做处理,要不然OPTIONS
的请求也会运行后端代码。一般预请求最好返回204
(NO-Content)。
在谷歌开发者工具
上查看网络请求时,如果预请求
是不在XHR
这个分类中,可以在Other
分类或者ALL
中查看。
什么时候会有预请求?
一般服务器默认允许GET
、POST
、HEAD
请求(前提跨域),所以这些请求,只要前端脚本不追加请求头,是不会有预请求发出的。这些请求叫简单请求
。
可以简单总结为只有GET
、POST
、HEAD
才可能没有预请求。
大多数浏览器不支持针对于预请求
的重定向
。如果一个预请求
发生了重定向
,浏览器将报告错误:
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
简单举个javascript代码例子:
有预请求:
fetch('http://localhost:3000/demo-01', {
method: 'get',
headers: {
//这里定义了请求头就一定会有预请求。
'Cache-Control': 'no-cache',
Pragma: 'no-cache'
},
})
无预请求:
fetch('http://localhost:3000/demo-01', {
method: 'get'
})
跨域流程图
一些名称说明
跨域请求
也是跨站请求
,叫法不一样。
HTTP请求头
也是HTTP请求首部
HTTP响应头
也是HTTP响应首部
跨域解决方案
正如大家所知,出于安全考虑,浏览器会限制脚本中发起的跨站请求。但是为了能开发出更强大、更丰富、更安全的Web应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。
Web 应用工作组( Web Applications Working Group )推荐了一种的机制,即跨源资源共享(Cross-Origin Resource Sharing (CORS)
。
跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。
跨域需要设置的HTTP首部字段
实现前后端跨域请求,需要设置下面相关的HTTP响应头:
字段名 | 必须设置与否 |
---|---|
Access-Control-Allow-Origin | 是 |
Access-Control-Allow-Credentials | 否 |
Access-Control-Allow-Methods | 否 |
Access-Control-Allow-Headers | 否 |
Access-Control-Max-Age | 否 |
Access-Control-Expose-Headers | 否 |
一般只要设置好 Access-Control-Allow-Origin
就可以跨域了,其他的字段都是配合使用的(其他字段有默认值)。
Access-Control-Allow-Origin
跨域允许就是这个字段设置的,默认不设置时不允许跨域。
origin
参数指定一个允许向该服务器提交请求的URI.对于一个不带有credentials
(即cookie)的请求,可以指定为'*'
,表示允许来自所有域的请求。如果想要更安全点,当然也可以指定URI。
例子
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials
此字段是用来设置是否允许传cookie
,默认为false
。
注意Access-Control-Allow-Credentials设置为true时,Access-Control-Allow-Origin不能为*,需要指定具体访问的域名来源。
Access-Control-Allow-Methods
默认值一般为GET
、HEAD
、POST
,所以delete
等方法的时候,默认会被限制。
指明资源可以被请求的方式有哪些(一个或者多个)。这个响应头信息在客户端发出预检请求的时候会被返。这就看需要了。
例子
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers
浏览器自身附带的请求头默认是被允许的,但是前端代码追加的请求头
,在跨域的时候是要被允许才可访问。
而且浏览器本身默认自带请求头
是不可修改的,如User-Agent
、Origin
等。
例子
Access-Control-Allow-Methods: token
Access-Control-Allow-Headers: *
Access-Control-Max-Age
这个响应头
告诉我们这次预请求
的结果的有效期是多久,有效期期间内的请求都不用使用预请求
。
Access-Control-Expose-Headers
CORS
请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定
后端设置拦截器
@Component
public class CrossDomainInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
String originHeader = httpServletRequest.getHeader("Origin");
httpServletResponse.setHeader("Access-Control-Allow-Origin", originHeader);
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Orgin,XMLHttpRequest,Accept,Authorization,authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With");
//需要注意的是,如果要发送Cookie,即前端Access-Control-Allow-Credentials设为true
//那么Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Accept-Ranges, Content-Encoding, Content-Length, Content-Range" + "," + "authorization");
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
}
springboot下设置跨域
/*跨域问题 springboot*/
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(1);
return bean;
}
声明:本文转载自 https://segmentfault.com/a/1190000006727486?utm_medium=referral&utm_source=tuicool