我是如何通过学习源码去解决react的一个小小的安全问题

背景

最近在复习前端安全问题,其中一个是css键盘追踪问题,在用户输入密码的地方嵌入第三方脚本来获取你的密码,具体详情可以去看看b站zs小满的具体讲解,这里就是要解决这个问题,我们看看如何实现。

具体问题分析

先来看看这个问题是什么样的,这个问题是只会出现在react以及类react框架之中,为什么有这个原因留给大家去思考。

js 复制代码
import { useEffect, useRef, useState } from "react"

const Home = () => {
   const InputRef = useRef(null)
   const [password, setPassword] = useState('')

    useEffect(() => {
        console.log(password);
    }, [password])
    const changePassword = (e:any) => {
        setPassword(e.target.value)

    }
    return <>
    <div className="home">
    <input type="password" ref={InputRef}  value={password} onChange={changePassword} />
     
    </div>
    
    </>
}
export default Home

代码很简单,我们就是在react里面写了一个受控的密码框组件,我们来看看问题是啥

我们可以看到,控制台里面我们竟然能够直接看到明文密码,我试了下生产环境,也是一样可以看到,密码属于敏感字段了,这样肯定不行。我就想,既然我用过多种组件库,我去看看组件库的input框有没有这个问题 于是我去试了antd的输入框

可以看到它是把value移除了,是做了一道处理,本着对知识的追求,我就去看了下antd的源码,学习下人家的解决思路。

源码学习

代码位置在components/input下面

关于visiable,icon,toggle的代码都是密码框前面那个眼睛图标控制密码显隐的,我们不用看

在49行这里有注释说,移除value,ok这不正是我们要看的地方嘛,可以看到,这里是用了一个自定义hook,我们可以进入hook去看看

useRemovePasswordTimeout.ts 复制代码
import { useEffect, useRef } from 'react';
import type { InputRef } from '../Input';

export default function useRemovePasswordTimeout(
  inputRef: React.RefObject<InputRef>,
  triggerOnMount?: boolean,
) {
  const removePasswordTimeoutRef = useRef<ReturnType<typeof setTimeout>[]>([]);
  const removePasswordTimeout = () => {
    removePasswordTimeoutRef.current.push(
      setTimeout(() => {
        if (
          inputRef.current?.input &&
          inputRef.current?.input.getAttribute('type') === 'password' &&
          inputRef.current?.input.hasAttribute('value')
        ) {
          inputRef.current?.input.removeAttribute('value');
        }
      }),
    );
  };

  useEffect(() => {
    if (triggerOnMount) {
      removePasswordTimeout();
    }

    return () =>
      removePasswordTimeoutRef.current.forEach((timer) => {
        if (timer) {
          clearTimeout(timer);
        }
      });
  }, []);

  return removePasswordTimeout;
}

这是那个hook的源码,我们看看做了啥事,首先接收一个ref ,然后又定义了一个removePasswordTimeoutRef 用来储存移除value的函数,然后定义了一个函数removePasswordTimeout来执行这个操作,内部操作往那个数组里面放定时器任务,这个任务的逻辑也很简单,就是判断传进来的ref绑定的dom元素是不是input并且是不是密码框,有没有value属性,有的话我们就移除,后面的useEffect我们不看trigger,直接看下面的return操作,就是每次重新调用时,清空上一次数组里面的定时器任务,后面将上面的那个方法暴露出去调用。

可以看到逻辑是真的简单。

实操

学习到上面的解决办法之后,就轮到我们去实现这个hook了

useRemovePasswordTimeout.ts 复制代码
import { useEffect, useRef } from "react"

export function useRemovePsswordValue(ref:any){
   
    const removePasswordRef = useRef<ReturnType<typeof setTimeout>[]>([])
    
    const removePasswordTimeout = () => {
        removePasswordRef.current.push(
            setTimeout(() => {
                if(ref.current && 
                    ref.current.getAttribute('type') === 'password'&&
                    ref.current.hasAttribute('value')
                    ){  
      
                        ref.current.removeAttribute('value')
                    }
            })
        )
    }
    useEffect(() => {
        return () => {
            removePasswordRef.current.forEach((timer) => {
                 if(timer){
                    clearTimeout(timer)
                 }
            })
        }
    }, [])
    return removePasswordTimeout
}

代码很相近,一个注意点,人家的password里面调用的input是封装过的,所以在current里面有一个input的对象,我们原生的input是没有这个属性的,需要注意下。

typescript 复制代码
import { useEffect, useRef, useState } from "react"
import {Input} from 'antd'
import {useRemovePsswordValue} from '../../hooks/useRemovePassword'
const Home = () => {

   const InputRef = useRef(null)
   const removePasswordValue =  useRemovePsswordValue(InputRef)
  
   const [password, setPassword] = useState('')
   const [apassword, setApassword] = useState('')
    useEffect(() => {
        console.log(password);
    }, [password])
    const changePassword = (e:any) => {
        setPassword(e.target.value)
        removePasswordValue()
    }
    const changeApassword = (e:any) => {
        setApassword(e.target.value)
    }

    return <>
    <div className="home">
    <input type="password" ref={InputRef}  value={password} onChange={changePassword} />
     <Input.Password onChange={changeApassword} value={apassword} />
    </div>
    
    </>
}
export default Home

回到我们的组件里面使用,看到效果如下

我们也是完美的实现了这个功能。

总结

对于面试千篇一律的sql注入,xss攻击 crsf等等老生常谈的安全问题,我们也可以去了解下其他的安全问题,拓展下自己的知识面,也可能面试的时候你说出来这种让面试官对你刮目相看一下。其次,学习源码的好处,能够让自己掌握别人解决问题的思路,提高自己的编码能力。最后,大家可以去思考下开头留下的问题,如果觉得有帮助的话,不妨动动手指点个赞😘!

相关推荐
小牛itbull10 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i19 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_21 分钟前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
guokanglun27 分钟前
空间数据存储格式GeoJSON
前端
GIS瞧葩菜31 分钟前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium
ZBY520311 小时前
【Vue】 npm install amap-js-api-loader指南
javascript·vue.js·npm
猫爪笔记1 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html
brief of gali1 小时前
记录一个奇怪的前端布局现象
前端
前端拾光者2 小时前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化