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
99
100
101
102
public class CaptchaServlet extends HttpServlet {
// 验证码宽度
private static int width = 80;
// 验证码高度
private static int height = 28;
// 验证码字符长度
private static int captchaCharLength = 4;
// 验证码的字符集
private static char[] captchaChars = "ABCEHKN23456789PRSTXYZ".toCharArray();
// 验证码的字符集大小
private static int captchaCharsSize = captchaChars.length;
// 验证码存储在 session 的键名
private static final String CAPTCHA_KEY = "_CAPTCHA_SESSION_KEY_";
private static final long serialVersionUID = 5438961220479548231L;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
String captchaText = generateRandomCode();
BufferedImage image = generateImage(captchaText);
HttpSession session = request.getSession();
session.setAttribute(CAPTCHA_KEY, captchaText);
try (OutputStream out = response.getOutputStream()) {
ImageIO.write(image, "jpg", out);
}
}
/**
* 生成随机码字符串
*/
private String generateRandomCode() {
Random random = ThreadLocalRandom.current();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < captchaCharLength; i++) {
builder.append(captchaChars[random.nextInt(captchaCharsSize)]);
}
return builder.toString();
}
/**
* 生成验证码图片
*
* @param captchaText
* 验证码字符串
* @return BufferedImage
*/
private BufferedImage generateImage(String captchaText) {
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = (Graphics2D) image.getGraphics();
// 设置背景颜色
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
// 设置边框
graphics.setColor(Color.GRAY);
graphics.drawRect(0, 0, width - 1, height - 1);
// 设置字体
graphics.setColor(Color.BLACK);
graphics.setFont(new Font("Calibri", Font.BOLD, height - 6));
// 平滑不锯齿
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int length = captchaText.length();
char[] chars = captchaText.toCharArray();
// 绘制验证码字符
for (int i = 0; i < length; i++) {
graphics.drawChars(chars, i, 1, ((width - 8) / length) * i + 7,
height / 2 + length / 2 + 6);
}
graphics.dispose();
return image;
}
}

web.xml

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>CaptchaServlet</servlet-name>
<servlet-class>org.fanlychie.servlet.CaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CaptchaServlet</servlet-name>
<url-pattern>/captcha</url-pattern>
</servlet-mapping>

JSP

1
<img src="captcha" onclick="this.src+=''" style="cursor:pointer;" title="看不清?换一个">

干扰线

在 graphics.dispose(); 前调用 drawLine(graphics);

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
private void drawLine(Graphics2D graphics) {
Point2D[] point2ds = getPoints(.1f, .3f, .5f, .9f);
graphics.setRenderingHints(new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
graphics.setColor(Color.BLACK);
graphics.setStroke(new BasicStroke(2));
for (int i = 0; i < point2ds.length - 1; i++) {
graphics.drawLine(
(int) point2ds[i].getX(),
(int) point2ds[i].getY(),
(int) point2ds[i + 1].getX(),
(int) point2ds[i + 1].getY());
}
}
private Point2D[] getPoints(float f1, float f2, float f3, float f4) {
Random rand = ThreadLocalRandom.current();
CubicCurve2D curve2d = new CubicCurve2D.Float(
width * f1, (height * (rand.nextInt(8) + 2)) * .1f,
width * f2, (height * (rand.nextInt(8) + 2)) * .1f,
width * f3, (height * (rand.nextInt(8) + 2)) * .1f,
width * f4, (height * (rand.nextInt(8) + 2)) * .1f);
PathIterator iterator = curve2d.getPathIterator(null, 3);
Point2D point2ds[] = new Point2D[200];
int count = 0;
float[] coords = new float[6];
while (!iterator.isDone()) {
switch (iterator.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
point2ds[count] = new Point2D.Float(coords[0], coords[1]);
}
count++;
iterator.next();
}
Point2D[] points = new Point2D[count];
System.arraycopy(point2ds, 0, points, 0, count);
return points;
}