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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
| /**
* 验证码生成工具类
*/
@Configuration
@ConditionalOnProperty(prefix = "login.verify.image", name = "enable")
@EnableConfigurationProperties(ImageCodeProperties.class)
public class ImageCodeUtil {
private Logger log = LoggerFactory.getLogger(ImageCodeUtil.class);
private ImageCodeProperties imageCodeProperties;
public ImageCodeUtil(ImageCodeProperties imageCodeProperties) {
this.imageCodeProperties = imageCodeProperties;
}
/**
* code为生成的验证码
* codePic为生成的验证码BufferedImage对象
*/
public ImageValidateCode generateCodeAndPic() {
Long expireDate = imageCodeProperties.getExpireDate(); // 过期时间
int imageWidth = imageCodeProperties.getImageWidth(); // 定义图片的width
int imageHeight = imageCodeProperties.getFontHeight(); // 定义图片的height
int codeCount = imageCodeProperties.getCodeCount(); // 定义图片上显示验证码的个数
int fontHeight = imageCodeProperties.getFontHeight(); // 字体高度
int offsetX = imageCodeProperties.getOffsetX(); // 验证码生成X轴间隔
int offsetY = imageCodeProperties.getOffsetY(); // 验证码生成Y轴间距
String fontName = imageCodeProperties.getFontName(); // 字体
char[] codeSequence = imageCodeProperties.getCodeSequence(); // 生成序列
// 定义图像buffer
BufferedImage buffImg = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
Graphics graphics = buffImg.getGraphics();
// 创建一个随机数生成器类
Random random = new Random();
// 将图像填充为白色
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, imageWidth, imageHeight);
// 创建字体,字体的大小应该根据图片的高度来定。
Font font = new Font(fontName, Font.BOLD, fontHeight);
// 设置字体。
graphics.setFont(font);
// 画边框。
graphics.setColor(Color.BLACK);
graphics.drawRect(0, 0, imageWidth - 1, imageHeight - 1);
// 随机产生30条干扰线,使图象中的认证码不易被其它程序探测到。
graphics.setColor(Color.BLACK);
for (int i = 0; i < 0; i++) {
int x1 = random.nextInt(imageWidth);
int y1 = random.nextInt(imageHeight);
int x2 = random.nextInt(imageWidth);
int y2 = random.nextInt(imageHeight);
graphics.drawLine(x1, y1, x1 + x2, y1 + y2);
}
// randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
StringBuffer randomCode = new StringBuffer();
int red = 0;
int green = 0;
int blue = 0;
// 随机产生codeCount数字的验证码。
for (int i = 0; i < codeCount; i++) {
// 得到随机产生的验证码数字。
String code = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);
// 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。设置为230避免使用纯白色
red = random.nextInt(230);
green = random.nextInt(230);
blue = random.nextInt(230);
// 用随机产生的颜色将验证码绘制到图像中。
graphics.setColor(new Color(red, green, blue));
graphics.drawString(code, (i + 1) * offsetX, offsetY);
// 将产生的四个随机数组合在一起。
randomCode.append(code);
}
ImageValidateCode imageCode = new ImageValidateCode(randomCode.toString(), buffImg);
if (expireDate != null){
imageCode.setExpireDate(Instant.now().getEpochSecond() + expireDate);
}
log.debug("生成图片验证码{}", imageCode.getCode());
return imageCode ;
}
}
@ConfigurationProperties(prefix = "login.verify.image", ignoreUnknownFields = true)
public class ImageCodeProperties {
private static final int DEFAULT_IMAGE_WIDTH = 90;
private static final int DEFAULT_IMAGE_HEIGHT = 90;
private static final int DEFAULT_CODE_COUNT = 4;
private static final int DEFAULT_FONT_HEIGHT = 18;
private static final int DEFAULT_IMAGE_OFFSET_X = 15;
private static final int DEFAULT_IMAGE_OFFSET_Y = 16;
private static final String DEFAULT_FONT_NAME = "微软雅黑";
private static final char[] DEFAULT_CODE_SEQUENCE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M','N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' };
private Long expireDate = 60L; // 过期时间
private int imageWidth = DEFAULT_IMAGE_WIDTH; // 定义图片的width
private int imageHeight = DEFAULT_IMAGE_HEIGHT; // 定义图片的height
private int codeCount = DEFAULT_CODE_COUNT; // 定义图片上显示验证码的个数
private int fontHeight = DEFAULT_FONT_HEIGHT; // 字体高度
private int offsetX = DEFAULT_IMAGE_OFFSET_X; // 验证码生成X轴间隔
private int offsetY = DEFAULT_IMAGE_OFFSET_Y; // 验证码生成Y轴间距
private String fontName = DEFAULT_FONT_NAME; // 字体
private char[] codeSequence = DEFAULT_CODE_SEQUENCE; // 生成序列
//=======================Getter/Setter=======================
}
/*
* 获得验证码的Controller
*/
@ConditionalOnProperty(prefix = "login.verify.image", value = "enable", havingValue = "true")
@RestController
public class ImageCodeController {
// spring工具类用于操作session
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Autowired
private ImageCodeUtil imageCodeUtil;
public static final String SESSION_IMAGE_VERIFY_CODE = "SESSION_IMAGE_VERIFY_CODE";
@GetMapping("/verify/image/code")
public void getVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageValidateCode imageCode = imageCodeUtil.generateCodeAndPic();
// 设置响应的类型格式为图片格式
response.setContentType("image/jpeg");
// 禁止图像缓存。
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 写入图片
ImageIO.write(imageCode.getCodePic(), "jpg", response.getOutputStream());
response.getOutputStream().flush();
// code放入session
imageCode.setCodePic(null);
sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_IMAGE_VERIFY_CODE,imageCode);
}
}
/**
* 验证码校验Filter,需要在BrowserSecurityConfig中在UsernamePasswordAuthenticationFilter
* 前配置http.addFilterBefore(imageValidateCodeFilter, UsernamePasswordAuthenticationFilter.class)
*/
public class ImageValidateCodeFilter extends OncePerRequestFilter {
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
public ImageValidateCodeFilter(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 是登陆请求而且为POST
if(StringUtils.equals("/authentication/commit", request.getRequestURI()) && StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) {
try {
validate(new ServletWebRequest(request));
filterChain.doFilter(request, response);
} catch (ValidateCodeException e) {
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
}
}else {
filterChain.doFilter(request, response);
}
}
private void validate(ServletWebRequest servletWebRequest) throws ServletRequestBindingException{
ImageValidateCode codeInSession = (ImageValidateCode) sessionStrategy.getAttribute(servletWebRequest, ImageCodeController.SESSION_IMAGE_VERIFY_CODE);
String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(),"imageCode");
if(StringUtils.isEmpty(codeInRequest)) {
throw new ValidateCodeException("提交的验证码不能为空");
}
if(codeInSession == null) {
throw new ValidateCodeException("验证码不存在");
}
if(codeInSession.compareExpired()) {
sessionStrategy.removeAttribute(servletWebRequest, ImageCodeController.SESSION_IMAGE_VERIFY_CODE);
throw new ValidateCodeException("验证码已过期");
}
if(!StringUtils.equalsIgnoreCase(codeInRequest, codeInSession.getCode())) {
throw new ValidateCodeException("验证码不匹配");
}
// 匹配成功
sessionStrategy.removeAttribute(servletWebRequest, ImageCodeController.SESSION_IMAGE_VERIFY_CODE);
}
}
|