同源策略与跨域解决
1、同源策略概念
同源策略(Same-Origin Policy)最早由NEtscape公司提出,是浏览器的一种安全策略。同源指的是协议、域名、端口号必须完成相同,违背同源策略就是跨域。
案例
- server.js
const express = require('express')
const app = express()
app.get('/home',(request,response) => {
// 当url为127.0.0.1:9000/home时,响应一个页面
response.sendFile(__dirname + '/index.html')
})
app.get('/data',(request,response) => {
// 当页面index.html中按钮被点击后响应数据
response.send('用户数据')
})
// 监听9000端口
app.listen('9000',() => {
console.log("服务启动,监听端口中...");
})
- 页面文件index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试同源策略</title>
</head>
<body>
<h3>hello</h3>
<button>点击获取用户数据</button>
<script>
const btn = document.querySelector('button')
btn.onclick = function () {
const xhr = new XMLHttpRequest()
// 这里满足同源策略,所以url可以简写:这里省略了域名http://127.0.0.1:9000
xhr.open('get', '/data')
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response);
}
}
}
}
</script>
</body>
</html>
浏览器输入127.0.0.1:9000/home
,得到如下结果
点击按钮,在控制台可以看到获取到了服务器响应的数据
2、解决跨域问题
2.1 方法一:jsonp解决跨域问题
- jsonp是什么
jsonp(JSON with Padding),是一个非官方的跨域解决方法,纯粹凭借程序员的聪明才智开发处理,只支持get请求。 - jsonp工作原理
在网页有一些标签自带跨域能力,例如img、link、iframe、script
JSONP就是利用script标签的跨域能力来发送请求的。
原生html实现
例如:
案例一:
- 测试用html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="result"></div> <!--跨域请求--> <!--vscode中使用liveserver打开网页时,端口号为5050,所以下面的script标签中确实跨域了--> <script src="http://127.0.0.1:8000/jsonp-test"></script> </body> </html>
- server.js文件【使用express框架模仿的服务器】
const express = require('express') const app = express() app.get('/jsonp-test',(request,response) => { // 响应一个字符串 response.send('hello jsonp') }) app.listen('8000',()=>{ console.log("服务启动,监听端口中..."); })
得到的结果报错,意思为不被期待的标识符,说的是script标签不能识别普通字符串
因为响应到页面的是一个字符串,而一个script标签是不能解析字符串的,而需要响应一个js代码,因此,server.js需要响应一个js代码的字符串const app = express() app.get('/jsonp-test',(request,response) => { // const data = { // name:"小刘" // } response.send('console.log("hello jsonp")') // response.send(`handle(${data})`) }) app.listen('8000',()=>{ console.log("服务启动,监听端口中..."); })
成功获取结果
打开浏览器网络,可以看到成功获取响应了,并且响应式js代码
案例二:
- server.js
const express = require('express') const app = express() app.get('/jsonp-test',(request,response) => { const data = { name:"小刘" } // 将data转换为json字符串形式 const str = JSON.stringify(data) // 利用模板字符串,将调用方法以js代码的格式响应过去 response.send(`handle(${str})`) }) app.listen('8000',()=>{ console.log("服务启动,监听端口中..."); })
- test.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="result"> </div> <script> // 将方法声明在这,而方法调用通过script标签跨域传进来 function handle(data){ const result = document.getElementById('result') result.innerHTML = data.name } </script> <!-- 实现跨域 --> <script src="http://127.0.0.1:8000/jsonp-test"></script> </body> </html>
查看浏览器网络,接收到的响应是一个方法调用的js代码,并且方法中的参数也是由另一端的服务器传过来的。
-
具体案例使用
-
案例描述:输入款输入用户名,判断用户名是否存在(这句逻辑就不写了)不存在,在输入框下显示"用户不存在",输入框样式改变。
步骤:
(1)动态创建一个script标签
(2)设置script的URL
(3)将script标签添加到页面中 -
模拟服务器端代码【express框架实现】
const express = require('express')
const app = express()
app.get('/test',(request,response) => {
// 这里省略逻辑判断
const data = {
exist:1,
msg:'用户名已经存在'
}
// 将data转换为json字符串形式
const str = JSON.stringify(data)
// 利用模板字符串,将调用方法以js代码的格式响应过去
response.send(`handle(${str})`)
})
app.listen('8000',()=>{
console.log("服务启动,监听端口中...");
})
- 页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
用户名:<input type="text" id="username">
<p></p>
<script>
const input = document.querySelector('input')
const p = document.querySelector('p')
// 声明函数
function handle(data){
input.style.border = 'solid 3px #f00'
p.innerHTML = data.msg;
}
input.onblur = function(){
// 获取用户的输入值
let username = this.value;
// console.log(this); // this标识的input整个标签元素
console.log(username);
// 创建script标签
const script = document.createElement('script')
// 设置标签的src属性
script.src = 'http://127.0.0.1:8000/test'
// 将script节点添加到文档中
document.body.appendChild(script)
}
</script>
</body>
</html>
效果展示:
jquery实现
$.ajax({
url:"http://www.test.com:8080/login",
type:'get', // jsonp只支持get请求
dataType:'jsonp', // 请求类型为jsonp
jsonpCallback:'handleCallback', // 自定义函数
data:{}, // 要传送的数据
})
vue实现
this.$http.jsonp('http://www.omain2.com:8080/login',{
params:{},
jsonp:'handleCallback',
}).then((res)=>{
console.log(res);
})
2.2 方法二:设置CORS响应头实现跨域
- CORS是什么
CORS(Cross-Oringin Resource Sharing),跨域资源共享。CORS是官方的跨域解决方案。
CORS特点是不需要再客户端做人任何特殊的操作,完全在服务器中进行处理,支持get和post请求,跨域资源共享标准增添了一组HTTP首部字段,允许服务器生命哪些源站通过浏览器有权限访问哪些资源。
-
CORS工作原理
CORS通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应后就会对响应’放行’ -
CORS的使用
server.js
// 引入express
const { response } = require('express')
const express = require('express')
// 创建应用对象
const app = express()
app.get('/server',(request,response) => {
// 设置响应头,实现跨域,第二个参数是指定可以跨域的地址,*表示所有
response.setHeader('Access-Control-Allow-Origin','*')
// 设置3s后响应
response.send('hello')
})
// 监听端口,启动服务
app.listen(8000,() => {
console.log("服务启动,监听8000端口中...");
})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ajax get请求</title>
<style>
#decorate{
width: 200px;
height: 100px;
border: dotted 3px pink;
}
</style>
</head>
<body>
<button id="btn">发送</button>
<div id="decorate"></div>
<script>
let btn = document.getElementById('btn')
let div = document.getElementById('decorate')
btn.onclick = function(){
const xhr = new XMLHttpRequest()
xhr.open('get','http://127.0.0.1:8000/server)
xhr.send()
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >=200 && xhr.status<300){
console.log(xhr.response); // 响应体
// 设置文本
div.innerHTML = xhr.response;
}
}
}
}
</script>
</body>
</html>
如果需要对所有的路由都解决跨域问题,那么可以写一个中间件,放在所有路由前面,如下
app.all('*', function (req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
next();
});
或者更简单点,引入一个别人已经写好的处理跨域请求的中间件,如下:
安装中间件
npm install cors
使用
app.use(cors())