思路:需要用户自定义设置宽度、高度、长度、以及验证码过期时间
- ImageCodeProperties.java 用户自定义图片信息
package com.imooc.security.core.properties;
/**
* URL 表示用户需要登陆验证的请求
* @author cjj
* @date 2018年10月29日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int length = 4;
private int exprieIn = 60;
private String url;
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;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getExprieIn() {
return exprieIn;
}
public void setExprieIn(int exprieIn) {
this.exprieIn = exprieIn;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
- ValidateCodeProperties.java 封装,因为后面还有一个短信验证码
package com.imooc.security.core.properties;
/**
* 默认验证
* @author cjj
* @date 2018年10月15日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
public ImageCodeProperties getImage() {
return image;
}
public void setImage(ImageCodeProperties image) {
this.image = image;
}
}
- SecurityProperties.java 读取properties 文件
package com.imooc.security.core.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 读取所有以imooc.security开头的properties配置项
* @author cjj
* @date 2018年9月21日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
private ValidateCodeProperties validate = new ValidateCodeProperties();
public ValidateCodeProperties getValidate() {
return validate;
}
public void setValidate(ValidateCodeProperties validate) {
this.validate = validate;
}
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}
- ValidateCodeGenerator.java 验证图片生成器接口
package com.imooc.security.core.validate.code;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
public interface ValidateCodeGenerator {
ImageCode createImage(ServletWebRequest request);
}
- ImageCodeGenertor.java 实现类
package com.imooc.security.core.validate.code;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import com.imooc.security.core.properties.SecurityProperties;
public class ImageCodeGenertor implements ValidateCodeGenerator{
private SecurityProperties securityProperties;
public ImageCode createImage(ServletWebRequest request) {
//获取用户设置的宽度
int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",
securityProperties.getValidate().getImage().getWidth());
//获取用户设置的高度
int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height",
securityProperties.getValidate().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.getValidate().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.getValidate().getImage().getExprieIn());
}
/**
* 生成的随机条纹码
* @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;
}
}
- ValidateCodeBeanConfig.java 生成spring javabean
package com.imooc.security.core.validate.code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.imooc.security.core.properties.SecurityProperties;
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
@Bean
@ConditionalOnMissingBean(name="imageCodeGenertor")//如果初始化spring容器的时候,里面有imageCodeGenerator则就不用加载下面的内容
public ValidateCodeGenerator imageCodeGenertor() {
ImageCodeGenertor codeGenertor = new ImageCodeGenertor();
codeGenertor.setSecurityProperties(securityProperties);
return codeGenertor;
}
}
- imooc-signIn.html 前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>标准登陆页面</h1>
<form action="/authentication/from" method="post">
<table>
<tr>
<td>用户名</td>
<td>
<input type="text" name="username">
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="password">
</td>
</tr>
<tr>
<td>图形验证码</td>
<td>
<input type="text" name="imageCode">
<img src="/code/image?width=200">
</td>
</tr>
<tr>
<td clospan="2">
<input type="submit" value="登陆">
</td>
</tr>
</table>
</form>
</body>
</html>
- ValidateCodeController.java 控制类
package com.imooc.security.core.validate.code;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
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.ServletRequestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import com.imooc.security.core.properties.SecurityProperties;
@RestController
public class ValidateCodeController {
protected static final String SESSION_KEY= "SESSION_KEY_IMAGE_CODE";
//在session 中添加一个sessionId
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Autowired
private ValidateCodeGenerator imageCodeGenertor;
@GetMapping("/code/image")
public void createImage(HttpServletRequest request,HttpServletResponse response) throws IOException {
//根据请求生成图片验证码对象
ImageCode imageCode = imageCodeGenertor.createImage(new ServletWebRequest(request));
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}
}
- ValidateCodeFilter.java 过滤
package com.imooc.security.core.validate.code;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import com.imooc.security.core.properties.SecurityProperties;
/**
* 实现InitializingBean接口,并实现初始化时要实现的方法afterPropertiesSet()
* OncePerRequestFilter确保在一次请求只通过一次filter,而不需要重复执行。
* @author cjj
* @date 2018年10月15日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{
private AuthenticationFailureHandler authenticationFailureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private Set<String> urls = new HashSet<>();
private SecurityProperties securityProperties;
//用来匹配URL /* 匹配0或者任意数量的字符 ?匹配任何单个字符 /** 匹配0或者任意目录
private AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public void afterPropertiesSet() throws ServletException {
// 项目启动时要做的事情
super.afterPropertiesSet();
String[] config = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getValidate().getImage().getUrl(),",");
for (String configUrl : config) {
urls.add(configUrl);
}
urls.add("/authentication/from");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println(request.getRequestURI());
System.out.println(request.getMethod());
boolean action = false;
for (String url : urls) {
if(pathMatcher.match(url, request.getRequestURI())) {
action = true;
}
}
/* if(StringUtils.equals("/authentication/from", request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) */
if(action){
try {
validate(new ServletWebRequest(request));
} catch(ValidateCodeException e) {
//返回异常信息
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
return ;
}
}
filterChain.doFilter(request, response);
}
/**
* @param request
* @throws ServletRequestBindingException
*/
private void validate(ServletWebRequest request) throws ServletRequestBindingException {
ImageCode codeInSession = (ImageCode)sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY);
//请求中拿到imageCode参数
String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
if(StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if(codeInSession == null) {
throw new ValidateCodeException("验证码不存在");
}
if(codeInSession.isExpried()){
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("验证码已过期");
}
if(!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
public SessionStrategy getSessionStrategy() {
return sessionStrategy;
}
public void setSessionStrategy(SessionStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
public Set<String> getUrls() {
return urls;
}
public void setUrls(Set<String> urls) {
this.urls = urls;
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
- ImageCode.java 图片验证码
package com.imooc.security.core.validate.code;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
/**
* 图片验证码
* @author cjj
* @date 2018年10月8日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
public class ImageCode {
//图片
private BufferedImage image;
//随机数
private String code;
//过期时间
private LocalDateTime expireTime;
public ImageCode() {
super();
}
public ImageCode(BufferedImage image, String code, int expireTime) {
super();
this.image = image;
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireTime);//生成一个过期的时间点
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
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 boolean isExpried() {
return LocalDateTime.now().isAfter(expireTime);//判断当前时间是否在过期时间之后
}
}
- ValidateCodeException.java 自定义异常处理
package com.imooc.security.core.validate.code;
import org.springframework.security.core.AuthenticationException;
/**
*
* @author cjj
* @date 2018年10月11日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
public class ValidateCodeException extends AuthenticationException {
/**
*
*/
private static final long serialVersionUID = 1L;
public ValidateCodeException(String msg) {
super(msg);
// TODO Auto-generated constructor stub
}
}
效果图
直接访问这个url时效果图
12. 当然用户可以自定义图形验证码生成代,例如
package com.imooc.code;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import com.imooc.security.core.validate.code.ImageCode;
import com.imooc.security.core.validate.code.ValidateCodeGenerator;
/**
* 以增量的方式去适应变化
* @author cjj
* @date 2018年10月26日
* @email 729165621@qq.com
* @blog blog.youkuaiyun.com/qq_29451823
*/
@Component("imageCodeGenertor")//直接生成Javabean
public class DemoImageCodeGenerator implements ValidateCodeGenerator {
@Override
public ImageCode createImage(ServletWebRequest request) {
System.out.println("更高级的图形验证码生成代码");
return null;//如果这样图片就不会显示
}
}
效果图