1.图形验证码
2.
package com.wx.core.validate.code;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
public class ImageCode {
private String code;
private LocalDateTime expireTime;
private BufferedImage image;
public ImageCode(BufferedImage image, String code, int expireIn) {
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
this.image = image;
}
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
this.code = code;
this.expireTime = expireTime;
this.image = image;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public LocalDateTime getExpireTime() {
return expireTime;
}
public void setExpireTime(LocalDateTime expireTime) {
this.expireTime = expireTime;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
}
/**
*
*/
package com.wx.core.validate.code;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import sun.security.util.SecurityConstants;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@RestController
public class ValidateCodeController {
private static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response)
throws Exception {
ImageCode imageCode = createImageCode(request);
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}
private ImageCode createImageCode(HttpServletRequest request) {
int height = 22;
int width = 68;
// 1.创建图片缓存区
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Random r = new Random();
// 2.创建绘制环境
Graphics paint = image.getGraphics();
Color c = new Color(200, 150, 255);
// 设置画笔
paint.setColor(c);
// 画背景
paint.fillRect(0, 0, width, height);
// 绘制数字和字母
StringBuffer codes = new StringBuffer();
char[] ch = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890".toCharArray();
int index;
for (int i = 0; i < 4; i++) {
index = r.nextInt(ch.length);
// 设置文本颜色
paint.setColor(new Color(r.nextInt(88), r.nextInt(150), r.nextInt(255)));
paint.drawString(ch[index] + "", (i * 16) + 3, 18);
codes.append(ch[index]);
}
paint.dispose();
return new ImageCode(image, codes.toString(), 60);
}
}
ok,把验证码的权限放开,然后访问登陆页面
现在要写校验逻辑,思路就是写一个过滤器,放到Security过滤器的链上,位置就放在UsernamePasswordAuthenticationFilter
前面,如果成功就调UsernamePasswordAuthenticationFilter,不成功抛出异常。
//OncePerRequestFilter保证过滤器只被掉一次
public class ValidateCodeFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler authenticationFailureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
if (StringUtils.equals("/authentication/form", httpServletRequest.getRequestURI())
&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) {
try {
validate(new ServletWebRequest(httpServletRequest));
} catch (ValidateCodeException e) {
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void validate(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
//从session中拿出验证码
ImageCode imageCode = (ImageCode) sessionStrategy.getAttribute(servletWebRequest, ValidateCodeController.SESSION_KEY);
//从请求中拿到imageCode这个参数,页面name为imageCode
String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if (imageCode == null) {
throw new ValidateCodeException("验证码不存在");
}
if (imageCode.isExpried()) {
throw new ValidateCodeException("验证码过期");
}
if (!StringUtils.equals(imageCode.getCode(), codeInRequest)) {
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeController.SESSION_KEY);
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(wxAuthenctiationFailureHandler);
http
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() //基于security默认表单登陆的方式
.loginPage("/authentication/require") //自定义登陆页面
.loginProcessingUrl("/authentication/form") //表单登陆提交的登陆请求地址
.successHandler(wxAuthenticationSuccessHandler) //自定义登陆成功后的处理
.failureHandler(wxAuthenctiationFailureHandler) //自定义登陆失败后的处理
.permitAll()
.and()
.authorizeRequests() //下面这些配置
.antMatchers("/authentication/require").permitAll() //登陆页面不需要权限认证就可以访问
.antMatchers(securityProperties.getBrowser().getLoginPage()).permitAll() //用户自定义的登陆页面有也要授权访问
.antMatchers("/error").permitAll() // 访问错误页面
.antMatchers("/code/image").permitAll() //生成验证码的请求
.anyRequest() //任何请求,都需要身份认证才能访问
.authenticated() 都需要认证
.and()
.csrf().disable();
}
没有配置跳转,登陆完场后就返回Authentication信息
重构代码
验证码配置类:
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
public ImageCodeProperties getImage() {
return image;
}
public void setImage(ImageCodeProperties image) {
this.image = image;
}
}
图片验证码配置类:
public class ImageCodeProperties {
//如果应用中用户没有配置验证码的参数,使用默认值如下
private int width = 67;
private int height = 23;
private int length = 4;
private int expireIn = 60;
public int getExpireIn() {
return expireIn;
}
public void setExpireIn(int expireIn) {
this.expireIn = expireIn;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Autowired
private SecurityProperties securityProperties;
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response)
throws Exception {
ImageCode imageCode = createImageCode(new ServletWebRequest(request));
//讲生成的验证码放进session
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}
private ImageCode createImageCode(ServletWebRequest request) {
//getIntParameter(ServletRequest request, String name, int defaultVal)
int height = ServletRequestUtils.getIntParameter(request.getRequest(),
"height", securityProperties.getCode().getImage().getHeight());
int width = ServletRequestUtils.getIntParameter(request.getRequest(),
"width", securityProperties.getCode().getImage().getWidth());
// 1.创建图片缓存区
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Random r = new Random();
// 2.创建绘制环境
Graphics paint = image.getGraphics();
Color c = new Color(200, 150, 255);
// 设置画笔
paint.setColor(c);
// 画背景
paint.fillRect(0, 0, width, height);
// 绘制数字和字母
StringBuffer codes = new StringBuffer();
char[] ch = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890".toCharArray();
int index;
for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
index = r.nextInt(ch.length);
// 设置文本颜色
paint.setColor(new Color(r.nextInt(88), r.nextInt(150), r.nextInt(255)));
paint.drawString(ch[index] + "", (i * 16) + 3, 18);
codes.append(ch[index]);
}
paint.dispose();
return new ImageCode(image, codes.toString(), securityProperties.getCode().getImage().getExpireIn());
}
}
ok 现在启动工程
验证码的参数可配置
ok,现在如果有多个请求需要需验证码该怎么配置?就是验证码拦截的接口可以配置
验证码生成逻辑可配置
把生成验证码的逻辑移到一个接口的实现里面去,然后做成可配置的
抽出一个验证码生成的接口来:
实现这个接口,作为系统的一个默认的配置
public class ImageCodeGenerator implements ValidateCodeGenerator {
/**
* 系统配置
*/
@Autowired
private SecurityProperties securityProperties;
@Override
public ImageCode generate(ServletWebRequest request) {
int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",
securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height",
securityProperties.getCode().getImage().getHeight());
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
String sRand = "";
for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}
g.dispose();
return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpireIn());
}
/**
* 生成随机背景条纹
*
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
再加一个配置类:
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator") //如果用户没用自定义实现imageCodeGenerator。就用这个默认的
public ValidateCodeGenerator imageCodeGenerator() {
ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
}
在生成的验证码里面去调用它:
用户可以自定义生成验证码的逻辑:
2.实现记住我的功能: