JS跨域(Access-Control-Allow-Origin)前后端解决⽅案
详解
浏览器的同源安全策略
同源策略,它是由Netscape提出的⼀个著名的安全策略。现在所有⽀持JavaScript的浏览器都会使⽤这个策略。所谓同源是指,域名,协议,端⼝相同。同源策略是浏览器的⾏为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是⽆法被浏览器接收。
同源:协议 + 域名 + 端⼝。所以,怎么才算跨域呢?
什么是跨域
什么是跨域,简单地理解就是因为JavaScript同源策略的限制,a 域名下的js⽆法操作b或是c.a域名下的对象。更详细的说明可以看下表:
URL说明是否允许通信
www.a/a.js
www.a/b.js同⼀域名下允许
www.a/lab/a.js
www.a/script/b.js同⼀域名下不同⽂件夹允许
www.a:8000/a.js
www.a/b.js同⼀域名,不同端⼝不允许
www.a/a.js
www.a/b.js同⼀域名,不同协议不允许
www.a/a.js
70.32.92.74/b.js域名和域名对应ip不允许
www.a/a.js
script.a/b.js主域相同,⼦域不同不允许
www.a/a.js
a/b.js同⼀域名,不同⼆级域名(同上)不允许(cookie这种情况下也不允许访问)
//www.jb51/a.js
www.a/b.js不同域名不允许
特别注意两点:
第⼀,如果是协议和端⼝造成的跨域问题“前台”是⽆能为⼒的,
第⼆:在跨域问题上,域仅仅是通过“URL的⾸部”来识别⽽不会去尝试判断相同的ip地址对应着两个域或两个域是否在同⼀个ip上。
“URL的⾸部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。
⼀、前端跨域解决⽅法(JavaScript)
接下来简单地总结⼀下在“前台”⼀般处理跨域的办法
1、document.domain+iframe的设置
html document是什么
www.a上的a.html
document.domain = 'a';
var ifr = ateElement('iframe');
ifr.src = 'script.a/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
var doc = tDocument || tWindow.document;
// 在这⾥操纵b.html
ElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
script.a上的b.html
document.domain = 'a';
这种⽅式适⽤于{www.jb51, jb51, script.jb51, css.jb51}中的任何页⾯相互通信。
备注:某⼀页⾯的domain默认等于window.location.hostname。主域名是不带www的域名,例如a,主域名
前⾯带前缀的通常都为⼆级域名或多级域名,例如www.a其实是⼆级域名。 domain只能设置为主域名,不可以在b.a中将domain设置为c.a。
问题:
1、安全性,当⼀个站点(b.a)被攻击后,另⼀个站点(c.a)会引起安全漏洞。
2、如果⼀个页⾯中引⼊多个iframe,要想能够操作所有iframe,必须都得设置相同domain。
2、动态创建script
虽然浏览器默认禁⽌了跨域访问,但并不禁⽌在页⾯中引⽤其他域的JS⽂件,并可以⾃由执⾏引⼊的JS⽂件中的function(包括操作cookie、Dom等等)。根据这⼀点,可以⽅便地通过创建script节点的⽅法来实现完全跨域的通信。
这⾥判断script节点加载完毕还是蛮有意思的:ie只能通过script的readystatechange属性,其它浏览器是script的load事件。以下是部分判断script加载完毕的⽅法。
if (!adyState || adyState === 'loaded' || adyState === 'complete') {
// callback在此处执⾏
}
};
3、利⽤iframe和location.hash
先是a下的⽂件cs1.html⽂件:
function startRequest(){
var ifr = ateElement('iframe');
ifr.style.display = 'none';
ifr.src = '//www.jb51/lab/cscript/cs2.html#paramdo';
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is '+data);
}
} catch(e) {};
}
setInterval(checkHash, 2000);
jb51域名下的cs2.html:
//模拟⼀个简单的参数处理操作
switch(location.hash){
case '#paramdo':
callBack();
break;
case '#paramset':
//do something……
break;
}
function callBack(){
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全机制⽆法修改parent.location.hash,
// 所以要利⽤⼀个中间的cnblogs域下的代理iframe
var ifrproxy = ateElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'a/test/cscript/cs3.html#somedata';    // 注意该⽂件在"a"域下
document.body.appendChild(ifrproxy);
}
}
a下的域名cs3.html
//因为parent.parent和⾃⾝属于同⼀个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
当然这样做也存在很多缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等……
4、window.name实现的跨域数据传输
⽂章较长列在此处不便于阅读,详细请看。
5、使⽤HTML5 postMessage
HTML5中最酷的新功能之⼀就是跨⽂档消息传输Cross Document Messaging。下⼀代浏览器都将⽀持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已经使⽤了这个功能,⽤postMessage⽀持基于web的实时消息传递。
otherWindow.postMessage(message, targetOrigin);
otherWindow: 对接收信息页⾯的window的引⽤。可以是页⾯中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
message: 所要发送的数据,string类型。
targetOrigin: ⽤于限制otherWindow,“*”表⽰不作限制
a/index.html中的代码:
<iframe id="ifr" src="b/index.html"></iframe>
<script type="text/javascript">
var ifr = ElementById('ifr');
var targetOrigin = 'b';  // 若写成'b/c/proxy.html'效果⼀样
// 若写成'c'就不会执⾏postMessage了
};
</script>
b/index.html中的代码:
<script type="text/javascript">
window.addEventListener('message', function(event){
// 通过origin属性判断消息来源地址
if (igin == 'a') {
alert(event.data);    // 弹出"I was there!"
alert(event.source);  // 对a、index.html中window对象的引⽤
// 但由于同源策略,这⾥event.source不可以访问window对象
}
}, false);
</script>
⼆、后台跨域解决⽅案
CORS
这是W3C的标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
//指定允许其他域名访问
‘Access-Control-Allow-Origin:172.20.0.206'//⼀般⽤法(,指定域,动态设置),3是因为不允许携带认证头和cookies
//是否允许后续请求携带认证信息(cookies),该值只能是true,否则不返回
‘Access-Control-Allow-Credentials:true'
上⾯第⼀⾏说到的Access-Control-Allow-Origin有多种设置⽅法:
@SpringBootApplication
@Configuration
@RestController
public class ApplicationA {
public static void main(String[] args) {
SpringApplication.run(ApplicationA.class, args);
}
@RequestMapping("/test")
public Object test(HttpServletRequest request, HttpServletResponse response) {
// 跨域⽀持
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
Map<String, Object> map = new HashMap<>();
map.put("success", true);
map.put("msg", "我来⾃服务端");
return map;
}
}
springboot⽀持跨域
测试⽤例是⼀个springboot项⽬,可以⽤更简单的⽅式。通过⼀个继承了WebMvcConfigurerAdapter的bean,重写addCorsMappings⽅法,在⽅法⾥配置。
@SpringBootApplication
@Configuration
@RestController
public class ApplicationA extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(ApplicationA.class, args);
}
@RequestMapping("/test")
public Object test(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> map = new HashMap<>();
map.put("success", true);
map.put("msg", "我来⾃服务端");
return map;
}
// 跨域⽀持
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
Java中设置多个Access-Control-Allow-Origin跨域访问
1、如果服务端是Java开发的,添加如下设置允许跨域即可,但是这样做是允许所有域名都可以访问,不够安全。response.setHeader(“Access-Control-Allow-Origin”,"*");
2、为保证安全性,可以只添加部分域名允许访问,添加位置可以在下⾯三处任选⼀个。
(1)可以在过滤器的filter的dofilter()⽅法种设置。
(2)可以在servlet的get或者post⽅法⾥⾯设置。
(3)可以放在访问的jsp页⾯第⼀⾏。
3、在此⽤第⼀种⽅法,注意l配置过滤器(filter)。
public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {
// 将ServletResponse转换为HttpServletResponse
HttpServletResponse httpResponse = (HttpServletResponse) res;
// 如果不是80端⼝,需要将端⼝加上,如果是集,则⽤Nginx的地址,同理不是80端⼝要加上端⼝
String []  allowDomain= {"www.baidu","123.456.789.10","123.16.12.23:8080"};
Set allowedOrigins= new HashSet(Arrays.asList(allowDomain));
String originHeader=((HttpServletRequest) req).getHeader("Origin");
if (ains(originHeader)){
httpResponse.setHeader("Access-Control-Allow-Origin", originHeader);
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");
// 如果要把Cookie发到服务器,需要指定Access-Control-Allow-Credentials字段为true
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Expose-Headers", "*");
}
chain.doFilter(req, res);
}
基于nginx配置请求的CORS
⽬前很多请求都不是直接暴露的,很多通过nginx做反向代理,因此可以使⽤nginx配置固定请求的Access-Control-Allow-Origin,实现跨域访问。⽅式是在被请求的接⼝,配置location代理,添加header实现。
#活动访问接⼝跨域配置
location /promotion/activityPro {
proxy_pass frontHost/promotion/activityPro;
add_header 'Access-Control-Allow-Origin' '*';
#add_header 'Access-Control-Allow-Methods' 'GET, POST';
#add_header 'Access-Control-Allow-Credentials' "true";
#add_header 'Access-Control-Max-Age' 86400;
#add_header 'Access-Control-Allow-Header' 'Content-Type,*';
}
三、前端跨域JSONP⽅案
有前端经验的童鞋知道,有时我们会在⾃⼰的代码⾥直接引⼊其它域名的js、css等静态⽂件。为啥这些静态⽂件没被浏览器限制呢?通常为了减轻web服务器的压⼒,我们会把js、css,img等静态资源分离到另⼀台独⽴域名的服务器上,使其和前端分离开。基于这个原因,浏览器并没有限制这类静态资源的跨域访问。
我们可以动态地创建⼀个script,让浏览器以为我们要获取静态资源,从⽽⽹开⼀⾯。⽽服务器端也需要做⼀点改变,不能直接返回json,⽽是返回⼀个⽴即执⾏的函数,⽽前端请求的结果就作为函数的参数。
后端接⼝返回
@SpringBootApplication
@Configuration
@RestController
public class ApplicationA {
public static void main(String[] args) {
SpringApplication.run(ApplicationA.class, args);
}
@RequestMapping("/test")
public String test(HttpServletRequest request, HttpServletResponse response, String callback)
throws IOException {
Map<String, Object> map = new HashMap<>();
map.put("success", true);
map.put("msg", "我来⾃服务端");
// 返回值如下:
// callback({"msg":"我来⾃服务端","success":true});
return String.format("%s(%s);", callback, Json(map));
}
js原⽣实现jsonp
function test() {
// 外部域名,参数是和后端接⼝约定的callback指定接⼝返回后的回调函数
url = "localhost:8882/test?callback=_ajax_callback";
// 创建⼀个script元素
var script = ateElement('script');
script.src = url;
document.head.appendChild(script);
}
// 接⼝回调
function _ajax_callback(res) {
console.log("被回调了");
console.log(res);
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。