图片校验码

纯前端实现

纯前端实现图片校验码指的是通过 canvas 绘制随机串的方式。这种方式有极大的弊端:如果随机串在前端生成,那么通过拦截伪造请求仍然能正常调用后台接口;如果随机串由后台发送给前端,那么中间可以被拦截并伪造(当然,可尝试用加密技术进行保护)。因此,这种方式大体适用于学习研究的目的。以下代码段基于 react 验证码组件 整理,其实现也可以参考 在React中随机生成图形验证码。您可以在 codesandbox 查看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import React, { useState, useRef, useEffect } from 'react';
import styles from './index.less';

const getRandom = (max: number, min: number, num?: number) => {
const asciiNum = ~~(Math.random() * (max - min + 1) + min)
if (!num) return asciiNum;

const arr = []
for (let i = 0; i < num; i++) {
arr.push(getRandom(max, min))
}

return arr
}

const VerifyCode = ({
code,
onRefresh,
}: {
code: string,
onRefresh?: () => void,
}) => {
const [rotate, setRotate] = useState(getRandom(15, -15, 4));
const [color, setColor] = useState([
getRandom(0, 255, 3), getRandom(0, 255, 3), getRandom(0, 255, 3), getRandom(0, 255, 3)
]);
const canvasRef = useRef<any>(null);

const refreshCanvas = () => {
const canvas = canvasRef.current;
if (canvas && canvas.getContext) {
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);

for (let i = 0; i < 4; i++) {
ctx.font = '80px Calibri'; //随机生成字体大小
ctx.fillStyle = `rgb(${color[i].toString()})`; //随机生成字体颜色
let x = canvas.width / (5) * (i + 1);
let y = canvas.height / 2;
let deg = rotate[i];
/**设置旋转角度和坐标原点**/
ctx.translate(x, y);
ctx.rotate(deg * Math.PI / 180);
ctx.fillText(code[i], 0, 0);

/**恢复旋转角度和坐标原点**/
ctx.rotate(-deg * Math.PI / 180);
ctx.translate(-x, -y);
}

/**绘制干扰线**/
for (let i = 0; i < 4; i++) {
ctx.strokeStyle = getRandom(100, 255, 3);
ctx.beginPath();
ctx.moveTo(getRandom(0, canvas.width), getRandom(0, canvas.height));
ctx.lineTo(getRandom(0, canvas.width), getRandom(0, canvas.height));
ctx.stroke();
}

/**绘制干扰点**/
for (let i = 0; i < canvas.width / 4; i++) {
ctx.fillStyle = getRandom(0, 255);
ctx.beginPath();
ctx.arc(getRandom(0, canvas.width), getRandom(0, canvas.height), 1, 0, 2 * Math.PI);
ctx.fill();
}
}
}

useEffect(() => {
refreshCanvas();
}, [code]);

const handleRefresh = () => {
setRotate(getRandom(15, -15, 4));
setColor([
getRandom(0, 255, 3), getRandom(0, 255, 3), getRandom(0, 255, 3), getRandom(0, 255, 3)
]);
onRefresh && onRefresh();
}

return (
<div className={styles.verify_code_wrap}>
<canvas className={styles.canvas} ref={canvasRef}></canvas>

<div
className={styles.refresh}
onClick={() => {
handleRefresh();
}}
>
看不清?换一个
</div>
</div>
)
}

export default VerifyCode;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.verify_code_wrap {
position: relative;
overflow: hidden;
width: 100%;
height: 11.73333vw;
}


.canvas {
width: 64%;
height: 100%;
}

.refresh {
display: inline-block;
text-align: center;
width: 30%;
height: 100%;
line-height: 11.73333vw;
vertical-align: top;
margin-left: 6%;
cursor: pointer;
color: #da2824;
}

后端传图片

后端传图片指的是后端制作图片校验码,或者将以 base64 的方式传递给前端,或者以纯图片的方式传递。若为 base64 数据,前端部分仅需指定 img 的 src 值。若为纯图片,须使用原生接口刷新图片,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default () => {
const imgWrap = useRef<HTMLDivElement>(null);
const handleRefresh = () => {
const image = new Image();
image.src = src;
imgWrap.current.replaceChild(image, imgWrap.current.firstChild);
}

return (
<div ref={imgWrap}>
{validCodeData && <img src={src} />}
<div
className={styles.refresh}
onClick={() => {
handleRefresh();
}}
>
看不清?换一个
</div>
</div>
)
}

后端部分可参考 Java实现图片验证码,结合 session 才能做完整校验。react中图片校验码实现以及new Buffer()使用方法 描述了使用 reponseType = arraybuffer 接取数据再转成 base64 的方式,未及尝试。arraybuffer 可参考 深入理解xhr的responseType中blob和arrayBuffer