产生原因
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
cookie/session 的安全机制
cookie: 一种身份验证的token 可参考SSO 同源策略的风险:
- CSRF攻击方式
如果没有同源策略,网站A登陆后,攻击者可以在其他网站使用保存在浏览器的A的cookie,来通过身份验证,进行非法操作。
- 就算有同源策略,也可能出现cookie篡改的风险
- 另一类风险为Dom查询
网站B通过Dom查询获取网站A的dom节点,获取敏感信息,同源策略对此做限制
cookie篡改
- 为了防止cookie篡改,在服务端对cookie进行加密。
- 在服务器中配置一个不为人知的字符串(我们叫它Secret),比如:
x$sfz32。当服务器需要设置Cookie时(比如authed=false),不仅设置authed的值为false, 在值的后面进一步设置一个签名,最终设置的Cookie是authed=false|6hTiBl7lVpd1P。 签名6hTiBl7lVpd1P是这样生成的:Hash('x$sfz32'+'false')。要设置的值与Secret相加再取哈希。 用户收到HTTP响应并发现头字段Set-Cookie: authed=false|6hTiBl7lVpd1P。 - 因为Cookie是明文传输的, 只要服务器设置过一次authed=true|xxxx我不就知道true的签名是xxxx了么, 以后就可以用这个签名来欺骗服务器了。因此Cookie中最好不要放敏感数据。 一般来讲Cookie中只会放一个Session Id,而Session存储在服务器端。
请求跨域解决
jsonP
原理
同源策略对script、图片标签不限制(被攻击风险小)
基础思想
- 前端通过一个script标签请求数据
<script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script> - 后端识别此类请求后返回回调函数string
"jsonpCb(){}" - 前端请求前定义了jsonpCb,因此请求返回后可执行jsonCb,在jsonCb可进行请求成功处理和script标签移除(防止页面script标签过多 ) 前端
/**
* JSONP请求工具
* @param url 请求的地址
* @param data 请求的参数
* @returns {Promise<any>}
*/
const request = ({url, data}) => {
return new Promise((resolve, reject) => {
// 处理传参成xx=yy&aa=bb的形式
const handleData = (data) => {
const keys = Object.keys(data)
const keysLen = keys.length
return keys.reduce((pre, cur, index) => {
const value = data[cur]
const flag = index !== keysLen - 1 ? '&' : ''
return `${pre}${cur}=${value}${flag}`
}, '')
}
// 动态创建script标签
const script = document.createElement('script')
// 接口返回的数据获取
window.jsonpCb = (res) => {
document.body.removeChild(script)
delete window.jsonpCb
resolve(res)
}
后端
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async jsonp (ctx) {
// 前端传过来的参数
const query = ctx.request.query
// 设置一个cookies
ctx.cookies.set('tokenId', '1')
// query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端,由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
}
}
module.exports = CrossDomain
CORS
是什么
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。
如何实现 在服务端的请求中间件上加上‘Access-Control-Allow-Origin’
ctx.use('Access-Control-Allow-Origin', 'http://ad.bytedance.com')
反向代理
什么是反向代理
正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。
如何实现
在nginx 上加上跨域配置
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}
Dom跨域解决
postMessage
实现方式 window.postMessage() 实现不同窗口不同页面的跨域通讯。 发送侧:
postMessage () {
const iframe = window.frames['crossDomainIframe']
iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
}
接收侧
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://crossdomain.com:9099') {
// 来自http://crossdomain.com:9099的结果回复
console.log(e.data)
}
})
document.domain
适用范围
这种方式只适合主域名相同,但子域名不同的iframe跨域。
实现方式
比如主域名是http://crossdomain.com:9099,子域名是http://child.crossdomain.com:9099,这种情况下给两个页面指定一下document.domain即document.domain = crossdomain.com就可以访问各自的window对象了。