跨域
-
1、通过jsonp跨域script 标签的src属性可以跨域引用文件,jsonp是请求之后后台包装好一段json,并且把数据放在一个callback函数,返回一个js文件,动态引入这个文件,下载完成js之后,会去调用这个callback,通过这样访问数据。
在浏览器端定义一个回调函数,并将函数名通过src传至服务器端;
服务器端将数据包装成为一段js数据,并返回js函数格式的js文件,接着拿到这个js文件之后函数自动调用,拿到后端返回的数据。 -
2、跨域资源共享(CORS)普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。
如果想实现当前页cookie的写入,在nginx反向代理中设置proxy_cookie_domain 和 NodeJs中间件代理中cookieDomainRewrite参数的设置。
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案 -
3、 nginx代理跨域跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。 -
4、WebSocket协议跨域WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,允许服务器端主动向浏览器端发送消息除了上面的4中外,还有几种可以参考的方式,具体使用可以根据实际情况来进行选型:
-
5、 document.domain + iframe跨域
-
6、 location.hash + iframe
-
7、 window.name + iframe跨域
-
8、 postMessage跨域
JSONP
一种常见的网络请求方式就是JSONP
使用JSONP最主要的原因往往是为了解决跨域访问的问题.
JSONP的核心在于通过
JSONP的缺点
只能发送get请求,因为script只能发送get请求
需要后台配合,此种情况需要前后台配合,将返回结果返回callback(result)的形式
Ajax、fetch、axios
- ajax:
【优点:局部更新;原生支持】
【缺点:可能破坏浏览器后退功能;嵌套回调】 - jqueryAjax:
【在原生的ajax的基础上进行了封装;支持jsonp】 - fetch:
【优点:解决回调地狱】
【缺点:API 偏底层,需要封装;默认不带Cookie,需要手动添加; 浏览器支持情况不是很友好,需要第三方的ployfill】 - axios:【几乎完美】
支持浏览器和node.js
支持promise
能拦截请求和响应
能转换请求和响应数据
能取消请求
自动转换JSON数据
浏览器端支持防止CSRF(跨站请求伪造)
Ajax、fetch、axios的区别与优缺点
原生ajax
//创建异步对象
var xhr = new XMLHttpRequest();
//设置请求基本信息,并加上请求头
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.open('post', 'test.php' );
//发送请求
xhr.send('name=Lan&age=18');
xhr.onreadystatechange = function () {
// 这步为判断服务器是否正确响应
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
};
jqueryAjax
var loginBtn = document.getElementsByTagName("button")[0];
loginBtn.onclick = function(){
ajax({
type:"post",
url:"test.php",
data:"name=lan&pwd=123456",
success:function(data){ console.log(data); }
});
}
fetch
fetch('http://www.mozotech.cn/bangbang/index/user/login', {
method: 'post',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams([ ["username", "Lan"],["password", "123456"] ]).toString()
})
.then(res => { console.log(res); return res.text();})
.then(data => { console.log(data);})
axios
axios({
method: 'post',
url: '/abc/login',
data: { userName: 'Lan', password: '123' }
})
.then(function (response) { console.log(response);})
.catch(function (error) { console.log(error);});
// 1.axios的基本使用-----------------------------------------------
axios({
url: 'http://123.207.32.32:8000/home/multidata',
// method: 'post'
}).then(res => { console.log(res); })
axios({
url: 'http://123.207.32.32:8000/home/data',
params: { type: 'pop', page: 1 } // 专门针对get请求的参数拼接
}).then(res => { console.log(res); })
// 2.axios发送并发请求---------------------------------------------------
axios.all([axios({ url: 'http://123.207.32.32:8000/home/multidata'}),
axios({ url: 'http://123.207.32.32:8000/home/data',
params: { type: 'sell', page: 5 }})])
.then(results => {
console.log(results);
console.log(results[0]);
console.log(results[1]);
})
// 3.使用全局的axios和对应的配置在进行网络请求--------------------------------
// 使用axios.all, 可以放入多个请求的数组.
//axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2
axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.timeout = 5000
axios.all([axios({ url: '/home/multidata'}),
axios({ url: '/home/data', params: { type: 'sell', page: 5 }
})]).then(axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
}))
// 4.创建对应的axios的实例
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance1({ url: '/home/multidata'
}).then(res => { console.log(res); })
instance1({
url: '/home/data',
params: { type: 'pop', page: 1 }
}).then(res => { console.log(res); })
const instance2 = axios.create({
baseURL: 'http://222.111.33.33:8000',
timeout: 10000,
// headers: {}
})
同时发送两个请求
function getUserAccount(){
return axios.get('/user/12345');
}
function getUserPermissions(){
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(),getUserPermissions()])
.then(axios.spread(function(acct,perms){
//当这两个请求都完成的时候会触发这个函数,两个参数分别代表返回的结果
}))
常见的配置选项
请求地址 url: '/user',
请求类型 method: 'get',
请根路径 baseURL: 'http://www.mt.com/api',
请求前的数据处理 transformRequest:[function(data){}],
请求后的数据处理 transformResponse: [function(data){}],
自定义的请求头 headers:{'x-Requested-With':'XMLHttpRequest'},
URL查询对象 params:{ id: 12 },
查询对象序列化函数 paramsSerializer: function(params){ }
request body data: { key: 'aa'},
超时设置s timeout: 1000,
跨域是否带Token withCredentials: false,
自定义请求处理 adapter: function(resolve, reject, config){},
身份验证信息 auth: { uname: '', pwd: '12'},
响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: 'json',
vue中简单使用axios
<template>
<h2>{{categories}}</h2>
</template>
<script>
import axios from 'axios'
export default {
name: "HelloWorld",
data() { return { categories: '' } },
created() {
axios({ url: 'http://123.207.32.32:8000/category'
}).then(res => { this.categories = res; })
}
}
</script>
封装到 request模块—回调方式
- 方式1
// ./network/request.js 文件
import axios from 'axios'
export function request(config, success, failure) {
const instance = axios.create({ // 1.创建axios的实例
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance(config) // 发送真正的网络请求
.then(res => { success(res); })
.catch(err => { failure(err) })
}
// main.js 文件
import {request} from "./network/request";
request({ url: '/home/multidata'},
res => { console.log(res); },
err => { console.log(err);
})
- 方式2
// ./network/request.js 文件
import axios from 'axios'
export function request(config) {
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance(config.baseConfig)
.then(res => { config.success(res); })
.catch(err => { config.failure(err) })
}
// main.js 文件
import {request} from "./network/request";
request({
baseConfig: { },
success: function (res) { },
failure: function (err) { }
})
封装到 request模块---promise axios封装
// ./network/request.js 文件
import axios from 'axios'
export function request(config) {
return new Promise((resolve, reject) => {
// 1.创建axios的实例对象
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 过滤器(拦截器)
instance.interceptors.response.use(res => { return res.data })
// 通过实例发送网络请求
instance(config)
.then(res => { resolve(res) })
.catch(err => { reject(err) })
})
//------------ 或者 -----------------------------------------------
import axios from 'axios'
export function request(config) {
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
return instance(config) // instance 本身就返回一个promise
}
// main.js 文件
import {request} from "./network/request";
request({ url: '/home/multidata' })
.then(res => { console.log(res); })
.catch(err => { console.log(err); })
将axios异步请求同步化处理
//使用 asyns/await
async getHistoryData (data) {
try {
let res = await axios.get('/api/survey/list/', { params: data})
this.tableData = res.data.result
this.totalData = res.data.count
} catch (err) { console.log(err) ; alert('请求出错!' }
}
axios拦截器的使用
拦截器分为request请求拦截器和response响应拦截器
request请求拦截器:发送请求前统一处理,如:设置请求头headers、应用的版本号、终端类型等。
response响应拦截器:有时候我们要根据响应的状态码来进行下一步操作,例如:由于当前的token过期,接口返回401未授权,那我们就要进行重新登录的操作。
// http request 请求拦截器 : 发送请求之前判断是否存在token,除了登录页,其他页面请求头headers都添加token
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
let pathname = location.pathname;
if(localStorage.getItem('token')){
if(pathname != '/' && pathname != '/login'){
config.headers.common['token'] = localStorage.getItem('token');
}
}
return config;
}, error => { return Promise.reject(error);});
---------------------------------------------------------------------------------
// http response 响应拦截器 : 若返回401,页面跳转到登录页面
axios.interceptors.response.use(response => {
return response;
},error => {
if (error.response) {
switch (error.response.status) {
case 401: // 返回401,清除token信息并跳转到登录页面
localStorage.removeItem('token');
router.replace({
path: '/login'
// query: {redirect: router.currentRoute.fullPath} //登录成功后跳入浏览的当前页面
})
}
return Promise.reject(error.response.data); // 返回接口返回的错误信息
}
});
认证Authentication, 授权Authorization, 凭证Credentials
什么是认证(Authentication)
验证当前用户的身份,证明“你是你自己”
用户名密码登录
邮箱发送登录链接
手机号接收验证码
什么是授权(Authorization)
用户授予第三方应用访问该用户某些资源的权限
你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)
实现授权的方式有:cookie、session、token、OAuth
什么是凭证(Credentials)
实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份
Session
什么是 Session
session 是另一种记录服务器和客户端会话状态的机制
session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
SessionID 是连接 Cookie 和 Session 的一道桥梁
session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
Token(令牌)
什么是 Token(令牌)
访问资源接口(API)时所需要的资源凭证
简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
token 的身份验证流程:
- 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
- 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
- token 完全由应用管理,所以它可以避开同源策略
JWT
JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。
是一种认证授权机制。
WT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户
之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名
服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。