关于跨域
前言
做前端的经常会接触一个名词:跨域,那何为跨域?为什么要跨域?
同源策略
出于安全考虑,浏览器会限制脚本中发起的跨域请求,比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略。 具体而言,默认情况下Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。
跨域
只要protocol
(如http/https/file
)、域名、端口三者有一个不一样即视为跨域。如www.aaa.com
和www.aaa.com:81
。
CORS
虽然浏览器的初衷是好的,为了安全,但是这样也大大限制了web的自由,鉴于此,又有了CORS
机制。CORS
全称Cross-Origin Resource Sharing
,中文含义为跨域资源共享。其原理就是服务器响应请求时返回一个特殊的header,这个header控制哪些网站可以跨域请求自己。
常见的一般有以下几个:
Access-Control-Allow-Origin: http://www.test.com
Access-Control-Allow-Headers: Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With
Access-Control-Allow-Methods: POST, GET, OPTIONS
很多时候浏览器会先发送一个 OPTIONS
请求给目的站点,来查明这个跨域请求对于目的站点是不是安全可接受的(当然也不是全部,比如GET请求就不会)。
需要特别之处的是,WebFont也有同源策略,所以如果跨域了的话也要添加如上header。
关于CORS,还有很多知识点,这里不作详细介绍,有兴趣的可以看HTTP访问控制(CORS) 。
关于cookie
CORS跨域访问时默认不发送cookie和http认证,如果要把Cookie发到服务器,那么需要:
- 服务端返回
Access-Control-Allow-Credentials:true
header; Access-Control-Allow-Origin
不能为*
;- js需要手动打开
withCredentials
属性:
原生JS开启跨域携带cookie示例:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://www.test.com/test_cors');
xhr.onreadystatechange= function()
{
if(xhr.readyState == 4) console.log(xhr.responseText)
}
xhr.send();
jQuery写法:
$.ajax({
url: 'xxx',
xhrFields: {
withCredentials: true
}
});
fetch
写法:
fetch('http://www.test.com/test_cors', {credentials: 'include'})
.then(resp => resp.json())
.then(json => console.log(json));
当credentials
开启时Access-Control-Allow-Origin
不能设置为*
,否则Chrome控制台会报如下错误:
A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true.
当然,这个没啥意义,因为真想设置为*
的话,服务端可以根据当前请求域名动态设置Origin
,以nginx为例(关键是这个$http_origin
):
server {
listen 80;
server_name www.test.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Headers' 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With, __session_token';
add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://127.0.0.1:8080;
}
}
但是,如果直接设置*
的话肯定很危险,因为所有网站都可以携带cookie随意跨域调用你的接口,假如你这个接口是需要登录才能调用的敏感信息接口,并且你没有手动做csrf措施的话,那这个接口100%有csrf的漏洞了。如果只需要针对信任的子域名开放跨域的话,可以这样设置:
location /test-cors {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
if ($http_origin ~* '\.haoji\.me$') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Headers' 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With';
add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS';
add_header 'Access-Control-Allow-Credentials' 'true';
}
proxy_pass http://127.0.0.1:8080;
}
非简单请求
//TODO 待完善
几种服务端的实现
Java实现
一般是用过滤器实现:
package com.test.filter;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 解决ajax跨域问题,一般现代浏览器都支持CORS,IE要求IE9+,安卓的webview也支持
* @date 20160114
* @author LXA
*/
public class CorsFilter implements Filter
{
// 允许跨域调用的网站,指定具体网站时只能填一个,所有网站时直接用*表示,开发时为了方便可以设置为*,上线后为了安全建议改成指定的地址
private String allow;
@Override
public void destroy()
{
System.out.println("CORS过滤器被销毁!");
}
@Override
public void doFilter(ServletRequest req, ServletResponse rsp, FilterChain filter) throws IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)rsp;
response.setHeader("Access-Control-Allow-Origin", allow);
response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
filter.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
System.out.println("初始化CORS过滤器!");
allow = filterConfig.getInitParameter("allow");
if(allow == null || "".equals(allow)) allow = "*";
}
}
然后在web.xml
中配置如下:
<!-- 支持ajax跨域的过滤器 -->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.test.filter.CorsFilter</filter-class>
<init-param>
<param-name>allow</param-name>
<!-- 允许跨域调用的网站,指定具体网站时只能填一个,所有网站时直接用*表示,开发时为了方便可以设置为*,上线后为了安全建议改成指定的地址 -->
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Nginx实现
server {
listen 80;
server_name localhost;
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With';
add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS';
root html;
index index.html index.htm;
}
}
apache实现
打开\apache\conf/httpd.conf
,搜索<Directory />
,然后添加如下3个Header
,重启apache生效:
<Directory />
AllowOverride none
Require all denied
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With"
Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
</Directory>
fiddler配置实现
使用fiddler下的Willow插件实现:
然后在最下面那个输入框添加Headers设置,冒号分隔,不需要引号,逐个添加:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With
参考
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS