Web 安全防御
在 axios
里提供了预防 csrf
攻击的功能,我们这一节学习下 axios
是如何预防 csrf
攻击的。首先是在 defaults/index.js
文件里的 defaults
配置对象中添加两个字段,其中 xsrfCookieName
字段对应的值是 cookie
名,用于从cookie中读取值,xsrfHeaderName
字段对应的值是请求头的字段名, 例如:headers['X-XSRF-TOKEN']。详细代码如下:
var defaults = {
xsrfCookieName: "XSRF-TOKEN",
xsrfHeaderName: "X-XSRF-TOKEN",
};
在配置对象中添加了这两个配置项之后,接下来就要在请求中携带 token 信息, 这部分的逻辑是在xhr.js中实现的
var utils = require("../utils");
var cookies = require("../helpers/cookies");
var isURLSameOrigin = require("../helpers/isURLSameOrigin");
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 省略以上部分代码,可以参考前面的内容或最后一章提供的源码资料
// 取消请求
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError("Request aborted", config, "ECONNABORTED", request));
request = null;
};
// 添加xsrf请求头
// config.withCredentials为true,或请求url跟当前域名同源时,才从cookies中读取xsrfValue
var xsrfValue =
(config.withCredentials || isURLSameOrigin(fullPath)) &&
config.xsrfCookieName
? cookies.read(config.xsrfCookieName)
: undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
// 省略下面部分代码,可以参考前面的内容或最后一章提供的源码资料
});
};
config.xsrfCookieName
配置项存在时,如果 config.withCredentials
为 true 时,或者当前 url
路径跟当前域名是同源环境,我们会读取 cookies
中的 config.xsrfCookieName
字段的值。否则,不添加 xsrf
请求头。
我们再看下 isURLSameOrigin
函数的代码,该函数是在 helpers/isURLSameOrigin.js
文件中实现的。我们首先判断当前的环境是不是标准浏览器环境,如果不是,直接返回一个不做任何判断的函数(isURLSameOrigin
函数直接返回 true)。如果是标准浏览器环境,我创建一个 a 标签,通过 a 标签分别来解析当前域名跟请求 url,然后判断它们的 protocol
跟 host
是否相同,如果相同则它们是同源 ur,否则不是。
"use strict";
var utils = require("../utils");
function standardBrowserEnv() {
var msie = /(msie|trident)/i.test(navigator.userAgent);
var urlParsingNode = document.createElement("a");
var originURL;
/**
* 解释一个url
*
* @param {String} url
* @returns {Object}
*/
function resolveURL(url) {
var href = url;
if (msie) {
// IE 浏览器需要设置两次来规范化属性
urlParsingNode.setAttribute("href", href);
href = urlParsingNode.href;
}
urlParsingNode.setAttribute("href", href);
// urlParsingNode提供的接口,详情见 - http://url.spec.whatwg.org/#urlutils
return {
href: urlParsingNode.href,
protocol: urlParsingNode.protocol
? urlParsingNode.protocol.replace(/:$/, "")
: "",
host: urlParsingNode.host,
search: urlParsingNode.search
? urlParsingNode.search.replace(/^\?/, "")
: "",
hash: urlParsingNode.hash
? urlParsingNode.hash.replace(/^#/, "")
: "",
hostname: urlParsingNode.hostname,
port: urlParsingNode.port,
pathname:
urlParsingNode.pathname.charAt(0) === "/"
? urlParsingNode.pathname
: "/" + urlParsingNode.pathname,
};
}
originURL = resolveURL(window.location.href);
/**
* 判断请求url跟当前的location是不是同源
*
* @param {String} requestURL 要测试的请求url
* @returns {boolean} 如果同源返回true,否则false。
*/
return function isURLSameOrigin(requestURL) {
var parsed = utils.isString(requestURL)
? resolveURL(requestURL)
: requestURL;
return (
parsed.protocol === originURL.protocol &&
parsed.host === originURL.host
);
};
};
module.exports = standardBrowserEnv()
我们再看下 cookies
函数的代码, 该函数是在 helpers/cookies.js
文件中实现的。我们只有在标准浏览器环境下才对 cookie
进行处理,代码非常简单,就是导出一个对象,对象里面包含了三个方法,其中 write
方法,用于写入 cookie
,read
方法用于读取 cookie
,remove
方法用于删除一个 cookie
。
"use strict";
var utils = require("../utils");
function standardBrowserEnv() {
return {
write: function write(name, value, expires, path, domain, secure) {
var cookie = [];
cookie.push(name + "=" + encodeURIComponent(value));
if (utils.isNumber(expires)) {
cookie.push("expires=" + new Date(expires).toGMTString());
}
if (utils.isString(path)) {
cookie.push("path=" + path);
}
if (utils.isString(domain)) {
cookie.push("domain=" + domain);
}
if (secure === true) {
cookie.push("secure");
}
document.cookie = cookie.join("; ");
},
read: function read(name) {
var match = document.cookie.match(
new RegExp("(^|;\\s*)(" + name + ")=([^;]*)")
);
return match ? decodeURIComponent(match[3]) : null;
},
remove: function remove(name) {
this.write(name, "", Date.now() - 86400000);
},
};
}
module.exports = standardBrowserEnv()
除了预防 csrf
攻击外,axios 还提供了 HTTP basic authentication
验证功能。我们可以在请求时在 config
对象中包含 auth
对象,然后把 username
和 password
通过 btoa
函数转为 base64
编码,通过 Authorization
请求头携带到后端去。
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 省略这部分代码
// 构造一个xhr对象
var request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth) {
var username = config.auth.username || "";
var password = config.auth.password
? unescape(encodeURIComponent(config.auth.password))
: "";
requestHeaders.Authorization = "Basic " + btoa(username + ":" + password);
}
});
};