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

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄4 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95274 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大4 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师4 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端