相关文章推荐
绅士的水煮肉  ·  excel vba outlook ...·  1 年前    · 
活泼的签字笔  ·  #undef directive ...·  1 年前    · 

1.使用Typescript重构axios(一)——写在最前面
2.使用Typescript重构axios(二)——项目起手,跑通流程
3.使用Typescript重构axios(三)——实现基础功能:处理get请求url参数
4.使用Typescript重构axios(四)——实现基础功能:处理post请求参数
5.使用Typescript重构axios(五)——实现基础功能:处理请求的header
6.使用Typescript重构axios(六)——实现基础功能:获取响应数据
7.使用Typescript重构axios(七)——实现基础功能:处理响应header
8.使用Typescript重构axios(八)——实现基础功能:处理响应data
9.使用Typescript重构axios(九)——异常处理:基础版
10.使用Typescript重构axios(十)——异常处理:增强版
11.使用Typescript重构axios(十一)——接口扩展
12.使用Typescript重构axios(十二)——增加参数
13.使用Typescript重构axios(十三)——让响应数据支持泛型
14.使用Typescript重构axios(十四)——实现拦截器
15.使用Typescript重构axios(十五)——默认配置
16.使用Typescript重构axios(十六)——请求和响应数据配置化
17.使用Typescript重构axios(十七)——增加axios.create
18.使用Typescript重构axios(十八)——请求取消功能:总体思路
19.使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式
20.使用Typescript重构axios(二十)——请求取消功能:实现第一种使用方式
21.使用Typescript重构axios(二十一)——请求取消功能:添加axios.isCancel接口
22.使用Typescript重构axios(二十二)——请求取消功能:收尾
23.使用Typescript重构axios(二十三)——添加withCredentials属性
24.使用Typescript重构axios(二十四)——防御XSRF攻击
25.使用Typescript重构axios(二十五)——文件上传下载进度监控
26.使用Typescript重构axios(二十六)——添加HTTP授权auth属性
27.使用Typescript重构axios(二十七)——添加请求状态码合法性校验
28.使用Typescript重构axios(二十八)——自定义序列化请求参数
29.使用Typescript重构axios(二十九)——添加baseURL
30.使用Typescript重构axios(三十)——添加axios.getUri方法
31.使用Typescript重构axios(三十一)——添加axios.all和axios.spread方法
32.使用Typescript重构axios(三十二)——写在最后面(总结)

项目源码请猛戳这里!!!

1. 前言

XSRF,即跨站请求伪造,它是前端常见的一种攻击方式,关于它的攻击原理以及一些常用的防范措施可以 猛戳这里查看 ,在这里我们主要介绍一种常用的防范措施,那就是在客户端与服务端首次登录确认身份成功后,服务端会颁发给客户端一个身份认证令牌,即 token ,客户端将其存储在 cookie 中,然后要求客户端以后每次请求都要携带此 token ,客户端往往会把这个 toekn 添加到请求的 headers 中,服务端接收到请求后,先从从请求 headers 中读取这个 token ,然后验证该 token 是否合法,如果合法则进行下一步操作,如果不合法,则直接拒绝服务。服务器端要求每次请求都包含一个 token ,这个 token 不在前端生成,而是在我们每次访问站点的时候生成,并通过 set-cookie 的方式种到客户端,然后客户端发送请求的时候,从 cookie 中对应的字段读取出 token ,然后添加到请求 headers 中。这样服务端就可以从请求 headers 中读取这个 token 并验证,由于这个 token 是很难伪造的,所以就能区分这个请求是否是用户正常发起的。

官方 axios 针对该防范措施已经为我们做好了一些基础工作,官方 axios 在默认请求配置对象中为我们提供了 xsrfCookieName xsrfHeaderName 这两个属性,其中 xsrfCookieName 表示存储 token cookie 名称, xsrfHeaderName 表示请求 headers token 对应的 header 名称。然后每次发送请求的时候,会自动从 cookie 中读取对应的 token 值,然后将其添加到请求 headers 中。

接下来,我们也要为我们实现的 axios 添加该功能。

2. 思路分析

要实现跟官方 axios 一样的功能,首先我们先来分解一下功能,梳理一下实现的思路:

  • 首先我们需要给请求配置对象 config 上添加 xsrfCookieName xsrfHeaderName 这两个属性的接口,并且将其写入默认请求配置对象中;
  • 因为跨站请求伪造是由于进行了跨域请求才有了被攻击的可能,而如果是同域请求那就不存在这个问题,所以我们需要先判断该请求是否跨域;
  • 由于上文所说的防范措施中需要携带 cookie ,所以我们还要判断上篇文章中添加的 withCredentials 是否为 true ,因为该属性表示请求是否允许携带 cookie ,如果不允许携带,那就没法防御了;
  • 如果上面两个判断都成功,则从 cookie 中根据 xsrfCookieName 来获取到 token 值;
  • 获取到以后,将 token 值添加到请求 headers 中, headers 中的属性名叫 xsrfHeaderName 的属性值;
  • OK,以上就是实现的整体思路,下面,我们就按照思路一步一步实现该功能。

    3. 向请求配置对象添加属性

    请求配置对象 config 中添加 xsrfCookieName xsrfHeaderName 这两个属性之前,我们需要先在 src/types/index.ts 中的配置对象的接口类型定义 AxiosRequestConfig 上添加该属性的定义,如下:

    export interface AxiosRequestConfig {
      // 新增
      xsrfCookieName?: string;
      xsrfHeaderName?: string
    

    添加好属性接口后,我们还要给默认请求配置对象中添加这两个属性,并且属性的默认值跟官方axios保持一直,如下:

    const defaults: AxiosRequestConfig = {
      // 新增
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
    

    4. 判断请求是否跨域

    判断请求是否跨域,即判断当前页面的url与请求的url是否同源,所谓同源,即两者的域名,协议,端口均相同,如有一个不同即为跨域,官方axios对于判断是否跨域使用了一个很巧妙的办法,它通过创建一个a标签的 DOM,然后设置该a标签的 href属性为我们请求的的url,然后这样就可以获取到该 DOMprotocolhostport。再把当前页面的 url也都通过这种方式获取,然后对比它们的 protocolhost以及port` 是否都相同,进而判断出请求是否跨域。那么接下来,我们也使用这种方法来判断请求是否跨域。

    我们在src/helpers目录下创建isURLSameOrigin.ts 文件,在该文件内创建isURLSameOrigin方法,用来判断请求是否跨域,如下:

    interface URLOrigin {
      protocol: string;
      host: string;
      port: string;
    export default function isURLSameOrigin(requestURL: string): boolean {
      let urlParsingNode = document.createElement("a");
      // 1.先获取当前页面地址的协议、域名、端口
      const currentOrigin = resolveURL(window.location.href);
      // 2.再获取请求url的协议、域名、端口
      const parsedOrigin = resolveURL(requestURL);
      // 3.最后比较三者是否相同
      return (
        parsedOrigin.protocol === currentOrigin.protocol &&
        parsedOrigin.host === currentOrigin.host &&
        parsedOrigin.port === currentOrigin.port
      // 创建一个可以通过url获取协议、域名、端口的函数
      function resolveURL(url: string): URLOrigin {
        urlParsingNode.setAttribute("href", url);
        return {
          protocol: urlParsingNode.protocol
            ? urlParsingNode.protocol.replace(/:$/, "")
            : "",
          host: urlParsingNode.host,
          port: urlParsingNode.port
    代码很简单,具体的细节也已经写进注释了。
    

    判断完请求是否跨域,还要判断该请求的withCredentials 属性是否为 true,这个就比较好判断了,直接获取请求配置对象config里的withCredentials 属性再判断就好了,就不细说了,接下来,如果这两个判断都成功,那么就需要从cookie中根据xsrfCookieName属性的属性值作为token的键来获取到token的值;

    5. 从cookie中获取token值

    如果上面两个判断都成功,那么我们就需要根据请求配置对象中xsrfCookieName属性的属性值作为键名从cookie中来获取token的值,由于浏览器对cookie的操作不是很友好,所以我们先封装一个从cookie中根据键名来获取值的辅助函数,我们在src/helpers目录下创建cookies.ts文件,在该文件中实现该辅助函数,如下:

    const cookie = {
      read(name: string): string | null {
        const match = document.cookie.match(
          new RegExp("(^|;\\s*)(" + name + ")=([^;]*)")
        return match ? decodeURIComponent(match[3]) : null;
    export default cookie;
    

    这个辅助函数实现过程很简单,就是在cookie中通过正则把传入的键名对应的值获取到。

    有了这个辅助函数后,我们只需调用cookie.read(xsrfCookieName)就可以获取到token值了,获取到token值以后把它添加到请求headers中,headers中的属性名叫xsrfHeaderName的属性值。

    6. 完整逻辑

    分解步骤已经全部实现好了,接下来就需要将所有的分解步骤按照逻辑组合在一起,首先,我们需要明确这一系列都是在发请求之前完成的,而且我们在这里为headers增加了东西,所以我们需要把这段逻辑写在处理headers之前,我们在src/core/xhr.ts中添加如下逻辑:

    const {
        // 新增
        xsrfCookieName,
        xsrfHeaderName
    } = config;
    let xsrfValue =
        (withCredentials || isURLSameOrigin(url!)) && xsrfCookieName
        ? cookies.read(xsrfCookieName)
        : undefined;
        if (xsrfValue) {
            headers[xsrfHeaderName!] = xsrfValue;
    

    OK,到此,我们就已经按照思路分析中将所有功能实现完毕了,接下来,我们就编写demo来测试效果如何。

    7. demo编写

    examples 目录下创建 defendXSRF目录,在 defendXSRF目录下创建 index.html:

    <!DOCTYPE html>
    <html lang="en">
        <meta charset="UTF-8" />
        <title>defendXSRF demo</title>
      </head>
        <script src="/__build__/defendXSRF.js"></script>
      </body>
    </html>
    

    接着再创建 app.ts 作为入口文件:

    import axios from "../../src/axios";
    axios
      .get("/api/defendXSRF", {
        xsrfCookieName: "XSRF-NLRX",
        xsrfHeaderName: "X-XSRF-NLRX",
        withCredentials: true
      .then(res => {
        console.log(res);
    

    接着在 server/server.js 添加新的接口路由:

    // 防御XSRF
    router.get("/api/defendXSRF", function(req, res) {
      res.cookie("XSRF-NLRX", "NLRX");
      res.json(req.cookies);
    

    在本demo中,我们为请求配置了xsrfCookieNamexsrfHeaderName以及withCredentials,并且在服务端给客户端种了 keyXSRF-NLRX,值为 NLRXcookie,作为 xsrftoken 值。然后我们在前端发送请求的时候,就能从 cookie 中读出 keyXSRF-NLRX 的值,然后把它添加到 keyX-XSRF-NLRX的请求 headers 中。

    最后在根目录下的index.html中加上启动该demo的入口:

    <li><a href="examples/defendXSRF">defendXSRF</a></li>
    

    OK,我们在命令行中执行:

    # 同时开启客户端和服务端
    npm run server | npm start
    

    接着我们打开 chrome 浏览器,访问 http://localhost:8000/ 即可访问我们的 demo 了,我们点击 defendXSRF,通过F12network 部分我们可以看到:请求已经正常发出,并且在请求的头部有我们添加的token

    OK,以上就是为axios添加防御XSRF攻击的功能。

  • 本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。
  • 博主在此发文(包括但不限于汉字、拼音、拉丁字母)均为随意敲击键盘所出,用于检验本人电脑键盘录入、屏幕显示的机械、光电性能,并不代表本人局部或全部同意、支持或者反对观点。如需要详查请直接与键盘生产厂商法人代表联系。挖井挑水无水表,不会网购无快递。
  • 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。
  • 博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的文章,请原谅博主成为一个无耻的文档搬运工!
  •