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