本文从web开发者角度,浅谈跨域原理,总结处理方法。
为什么会有跨域问题?
简单来说,浏览器不允许访问除当前页面所在源之外的其他源。
协议、域名、端口组成同一源(origin)
在前后端不分离的单体应用中,我们访问的前端页面和他的后端接口通常处于同一个端口下,因此可以直接访问;
很多web开发者第一次接触跨域请求应该是在学习前后端分离的B/S应用时。
由于浏览器的安全性限制,不允许 AJAX 访问协议不同、域名不同、端口号不同的数据接口,否则会出报 No 'Access-Control-Allow-Origin' header is present on the requested resource.
错误。它是由浏览器的同源策略造成的,是浏览器对JavaScript
施加的安全限制。
一些例子:
http://www.tablerows.top
--> http://admin.tablerows.top
跨域
http://www.tablerows.top
--> http://www.tablerows.top
非跨域
http://www.tablerows.top
--> http://www.tablerows.top:8080
跨域
http://www.tablerows.top
--> https://www.tablerows.top
跨域(协议改变)
同源策略控制不同源之间的交互,例如在使用XMLHttpRequest
或 <img>
标签时则会受到同源策略的约束。通常允许跨源写和资源嵌入(如<img>
嵌入其他源的图片),但不允许跨源读。
拓展:
https://www.cnblogs.com/cbs-writing/p/10544203.html
浏览器发起请求后都发生了什么(计算机网络视角)
http是无状态无连接的,但为了收到回应需要连接,所以先用tcp建立连接,最后再断开
简单来说,浏览器会禁止当前访问的页面请求其他源的资源。我们可以认为当前源是安全的(默认当前源是用户主动要访问的),但此时由程序发起请求访问的其他源却不是用户主动进行的行为,而这可能存在风险。
不论是请求的源还是响应的源,都不欢迎跨域。
用户的cookies信息只在当前源下有用,同源策略可以阻止一个页面上的恶意脚本通过页面的DOM对象获得访问另一个页面上敏感信息的权限,如获取cookie。
如图,像cookie中存储了当前源的信息。如果当前文档随意访问其他源的资源并带回来了不好的东西(脚本之类),会有危险。
当我们访问了一个恶意网站 如果没有同源策略 那么这个网站就能通过js 访问document.cookie 得到用户关于的各个网站的sessionID ,可以对服务器发送CSRF攻击。
api跨域:
有些公共Api也没有设置跨域访问。此时有三个源:我们访问的前端项目、后端项目、公共api。可以通过代理的方式,让浏览器(一直在访问前端)一直访问代理,代理做跨域配置
虽然一直在访问nginx,但是代理服务器如果只做转发的话,http请求中的数据没有动,浏览器解析时还是会当作跨域请求(检查header?)
跨域攻击实战
比如在B站看到的油猴脚本获取表单内容、获取token等(当然社工学要起作用比如让你掉线)
所谓其他域的恶意脚本是否就是这个?
就算是html文档,也有js?
防跨域是为了自己域的东西都有保证,,请求的其他域不一定是自己设计的,,,默认用户主动访问的这个域是安全的
虽然现在接口经常返回json数据,但之前响应内同是文档(html等万维网文档)
使用 CORS(跨资源共享)解决跨域问题
之前我们讨论了“为什么浏览器要阻止跨域”,但事实上很多应用都需要跨域来实现功能。因此现在大多浏览器都支持CORS,只需设置服务端也支持CORS即可。而支持CORS使浏览器请求某个资源的过程多了几个步骤。
原理浅析
CORS 是一个 W3C 标准,全称是"跨源(域)资源共享"(Cross-origin resource sharing)。它由一系列传输的HTTP头(CORS头,包含于HTTP头之中)组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。
-
Ajax基于XMLHttpRequset对象实现的,而各种请求库又是对Ajax技术的实现、封装
-
Fetch API
-
```
Failed to fetch version info for CodeFarmer1999/img_cloud.
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于前端开发者来说,CORS 通信与同源的 平时没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
跨源资源共享(CORS) - HTTP | MDN (mozilla.org)
简单来说,现在的浏览器允许发出跨院请求对于收到的response,
CORS有两种处理方式,一种是只在请求头和响应头增加CORS头的信息,另一种是发送预检请求
-
增加Header信息(示例中省去部分请求头和相应头信息)
在简单模式下的跨域请求(详情见官方文档)不需要发送预检请求,只需单纯的增加Header信息即可。
满足简单请求的3个条件:
-
请求方法为:GET/POST/HEAD 之一;
-
当请求方法为POST时,Content-Type是application/x-www-form-urlencoded,multipart/form-data或text/plain之一;
-
没有自定义请求头;
-
-
不使用cookie;
以下是浏览器发送给服务器的请求报文:
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Origin: https://foo.example
请求首部字段 Origin 表明该请求来源于
http://foo.example
。HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2 Access-Control-Allow-Origin: * [XML Data]
本例中,服务端返回的 `Access-Control-Allow-Origin: *` 表明,该资源可以被 **任意** 外域访问。
`Access-Control-Allow-Origin` 响应头指定了该响应的资源是否被允许与给定的origin共享。
- **发送预检请求 Preflight request**
一个 CORS 预检请求是用于检查服务器是否支持 [CORS](https://developer.mozilla.org/zh-CN/docs/Glossary/CORS) 即跨域资源共享。
它一般是用了以下几个 HTTP 请求首部的 [`OPTIONS`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/OPTIONS) 请求:[`Access-Control-Request-Method`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Request-Method) 和 [`Access-Control-Request-Headers`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Request-Headers),以及一个 [`Origin`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Origin) 首部。
当有必要的时候,浏览器会自动发出一个预检请求;所以在正常情况下,前端开发者不需要自己去发这样的请求。
举个例子,一个客户端可能会在实际发送一个 `DELETE` 请求之前,先向服务器发起一个预检请求,用于询问是否可以向服务器发起一个 [`DELETE`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/DELETE) 请求:
```http
OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
如果服务器允许,那么服务器就会响应这个预检请求。并且其响应首部 [`Access-Control-Allow-Methods`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) 会将 `DELETE` 包含在其中:
```http
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
```
同时满足下列以下条件,就属于简单请求,否则属于非简单请求(参考HTTP访问控制(CORS))
1.请求方式只能是:GET、POST、HEAD
2.HTTP请求头限制这几种字段(不得人为设置该集合之外的其他首部字段):
Accept、Accept-Language、Content-Language、Content-Type(需要注意额外的限制)、DPR、Downlink、Save-Data、Viewport-Width、Width
3.Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
4.请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
5.请求中没有使用 ReadableStream 对象。
预检请求的结果可能被缓存
若后端设置Access-Control-Allow-Origin:*,当前端携带Credentials发来请求时,可能会遇到withCredentials问题(前后端都可能遇到报错)(
毕竟公交车不在意别人身份证)。此时可以设置后端只允许部分域名访问(推荐,因为安全),或是设置请求头中不携带凭证。Access-Control-Allow-Credentials:
唯一有效值为true。如果不需要credentials,相比将其设为false,MDN更建议忽视这个头。当然,很多后端代码实现可以提供设置为false的方法
作为普通响应时:表示是否可以将对请求的响应暴露给页面。返回true则可以,其他值均不可以。
- 如Get请求时,浏览器不会发送预检请求。如果服务端回来的响应头中没有该项或不为true,则不会显示响应内容。
最为预检请求的响应时:表示是否真正的请求可以使用credentials。
- 如果不允许对资源的请求头携带凭证(如Access-Control-Allow-Origin:*默认不允许),假如请求头真的带了凭证,反而会报错。就好像问公交车需不需要提供身份证,人家会觉得你太正经。
- Credentials可以是 cookies, authorization headers (token)或 TLS client certificates。
下面展示开发过程中如何解决跨域问题。注意不论是通过代码、配置文件进行配置,大多都是基于CORS标准诞生的解决方法。
采用一种方法解决问题即可!我们的目的是在响应头中添加允许跨域的信息!
post发送两次请求,第一次返回码100?不是rfc文档要求的,是curl有这个实现
利用代理服务器配置
####IIS配置:
只需要在IIS添加HTTP响应标头即可
Access-Control-Allow-Headers:Content-Type, api_key, Authorization
Access-Control-Allow-Origin:*
####Apache配置:修改http.conf
<Directory "/Users/cindy/dev">
AllowOverride ALL
Header set Access-Control-Allow-Origin *
</Directory>
或者,修改Apache伪静态规则文件.htaccess
####Nginx(推荐使用)
修改nginx.conf,用add_header来为响应头添加信息
location ~* .(eot|ttf|woff|svg|otf)$ {
add_header Access-Control-Allow-Origin *;
}
- 为什么nginx确定是添加到响应了啊喂
前端开发时代理服务器
vue代理
proxyTable
dev{
proxyTable: {
'/api': {
target: 'http://192.168.0.1:200', // 要代理的域名
changeOrigin: true,//允许跨域
pathRewrite: {
'^/api': '' // 这个是定义要访问的路径,名字随便写
}
}
}
配置好之后由http-proxy-middleware中间件做跨域
webpack
https://blog.csdn.net/weixin_34355715/article/details/91413583
node的serve
https://blog.csdn.net/echo008/article/details/78307841
springboot
注解
直接在需要跨域访问的类或方法上配置@CrossOrigin注解即可。
扩展:@CrossOrigin 注解还支持更加丰富的参数配置:
value:表示支持的域。这里表示来自 http://localhost:8081 域的请求是支持跨域的。默认为 *,表示所有域都可以。
maxAge:表示探测请求的有效期(先进行判断是否有效)。探测请求不用每次都发送,可以配置一个有效期,有效期过了之后才会发送探测请求。默认为 1800 秒,即 30 分钟。
allowedHeaders:表示允许的请求头。默认为 *,表示该域中的所有的请求都被允许。
@CrossOrigin(value = "http://localhost:8081", maxAge = 1800, allowedHeaders ="*")
全局配置:
创建webMvc配置类
全局配置需要添加自定义类继承WebMvcConfigurer类,然后实现接口中的 addCorsMappings 方法。下面是一个简单的样例代码,请根据需要配置:
从上往下依次为:
addMapping: 表示对哪种格式的请求路径进行处理。
allowedHeaders: 表示允许的请求头,默认允许所有的请求头信息。也可以在这里配置请求方法。
allowedMethods: 表示允许的请求方法,默认是 GET、POST 和 HEAD。这里配置为 * 表示支持所有的请求方法。
maxAge: 最大响应时间
allowedOrigins: 该域发出的请求允许跨域
.allowCredentials(true): 允许携带token
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/controller/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("http://localhost:8081")
.allowCredentials(true);
}
}
配置专门的跨域配置类
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 解决跨越配置类
*/
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
/**
* 跨域过滤器
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
过滤器
import javax.servlet.*;
@Component
public class CorsFilter implements Filter {
// 只有在列表中的才允许访问
private final List<String> allowedOrigins = Arrays.asList("http://localhost:8089");
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// Lets make sure that we are working with HTTP (that is, against HttpServletRequest and HttpServletResponse objects)
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Access-Control-Allow-Origin
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "*");
response.setHeader("Vary", "Origin");
// Access-Control-Max-Age
response.setHeader("Access-Control-Max-Age", "3600");
// Access-Control-Allow-Credentials
response.setHeader("Access-Control-Allow-Credentials", "true");
// Access-Control-Allow-Methods
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
// Access-Control-Allow-Headers
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");
}
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {
}
}
网关配置
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOriginPatterns: "*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"
其他
websocket再配置类中也有自己的方法。
- 待实验
- 如何允许多个域?
- 配置了webmvc跨域,还需要配websocket吗?(会覆盖么?)
- 协议升级?
jsonp
Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
Padding这里我们理解为填充。pad有软垫的意思,ipad在刚推出时曾被吐槽过女性卫生用品(#iTampon),,
<script type="text/javascript" src="http://localhost:20002/test.js"></script>
程序A中sample的部分代码:
<script type="text/javascript">
//回调函数
function callback(data) {
alert(data.message);
}
</script>
<script type="text/javascript" src="http://localhost:20002/test.js"></script>
程序B中test.js的代码:
//调用callback函数,并以json数据形式作为阐述传递,完成回调
callback({message:"success"});
这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。
将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义吧。
标签的src属性并不被同源策略所约束,所以可以获取任何服务器上脚本并执行。五、jQuery对JSONP的实现
jQuery框架也当然支持JSONP,可以使用$.getJSON(url,[data],[callback])
方法(详细可以参考JQ实现jsonp)。
那我们就来修改下程序A的代码,改用jQuery的getJSON方法来实现(下面的例子没用用到向服务传参,所以只写了getJSON(url,[callback])):
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$.getJSON("http://localhost:20002/MyService.ashx?callback=?",function(data){
alert(data.name + " is a a" + data.sex);
});
</script>
结果是一样的,要注意的是在url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。这个函数名大家可以debug一下看看,比如jQuery17207481773362960666_1332575486681。
当然,加入说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?我们可以使用$.ajax
方法来实现(参数较多,详细可以参考http://api.jquery.com/jQuery.ajax)。先来看看如何实现吧:
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$.ajax({
url:"http://localhost:20002/MyService.ashx?callback=?",
dataType:"jsonp",
jsonpCallback:"person",
success:function(data){
alert(data.name + " is a a" + data.sex);
}
});
</script>
没错,jsonpCallback就是可以指定我们自己的回调方法名person,远程服务接受callback参数的值就不再是自动生成的回调名,而是person。dataType是指定按照JSOPN方式访问远程服务。
或者简单的模拟一下?
[跨源资源共享(CORS) - HTTP | MDN (mozilla.org)
这种方式又称为隐藏跨域,浏览器一直认为访问的资源没有跨域,实际是服务器做了处理。我们开发的时候经常使用,比如:在vue.config.js或是webpack.config.js中配置devServer的相关参数来实现代理,或者是使用nginx做代理。
看看浏览器源码怎么做跨域、预检请求
等等,,有后面i请求头了还要预检干什么,,
不管我们有没有返回header,浏览器都会向我们这个服务发送这个请求。浏览器在发送这个请求的时候,并不知道这个服务是不是跨域。
Access to XMLHttpRequest at ‘file:///C:/Users/hp/Desktop/trIndex-wallpaper/undefinedv3/?id=undefined’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome-extension, edge, https, chrome-untrusted.
另外cookie是和域名绑定在一起的,其他网站并不能访问这个网站储存的cookie。不过前提是储存cookie的网站未做跨域处理。
比如google账号,登录一次就可以免登录访问所有谷歌旗下的网站,这就是做了处理的。