您的位置:首页 > 编程语言 > Java开发

(八)、SpringBoot 图形验证码实现

2018-03-15 16:58 519 查看
可以前往第一篇博客查看目录结构 --> 这里

一、在core模块validate包下创建一个通用验证码实体类 -> ValidateCode (包含验证码、过期时间、判断是否过期的方法)public class ValidateCode {

private String code;

private LocalDateTime expireTime;

public ValidateCode(String code, int expireTime) {
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
}

public ValidateCode(String code, LocalDateTime expireTime) {
this.code = code;
this.expireTime = expireTime;
}

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;
}

/**
* 判断时间是否过期
* @return
*/
public boolean isExpried() {
return LocalDateTime.now().isAfter(expireTime);
}
}

二、创建图形验证码实体类ImageCode 继承于 ValidateCode     (新增image属性,保存验证图片)public class ImageCode extends ValidateCode{

private BufferedImage image;

public ImageCode(BufferedImage image, String code, int expireTime) {
super(code, expireTime);
this.image = image;
}

public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
super(code, expireTime);
this.image = image;
}

public BufferedImage getImage() {
return image;
}

public void setImage(BufferedImage image) {
this.image = image;
}
}
三、创建图形验证码java配置文件,自定义需要的属性public class ImageCodeProperties {

private int width = 67;
private int height = 23;
private int length = 4;
private int expireIn = 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 getExpireIn() {
return expireIn;
}

public void setExpireIn(int expireIn) {
this.expireIn = expireIn;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}
}
四、在ImageCodeProperties上封装多一层通用的ValidateCodePropertiespublic class ValidateCodeProperties {

private ImageCodeProperties image = new ImageCodeProperties();

public ImageCodeProperties getImage() {
return image;
}

public void setImage(ImageCodeProperties image) {
this.image = image;
}
}

五、在SecurityProperties中new 一个ValidateCodeProperties ,集中配置@ConfigurationProperties(prefix = "zeke.security")
public class SecurityProperties {

private BrowserProperties browser = new BrowserProperties();

private ValidateCodeProperties code = new ValidateCodeProperties();

public BrowserProperties getBrowser() {
return browser;
}

public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}

public ValidateCodeProperties getCode() {
return code;
}

public void setCode(ValidateCodeProperties code) {
this.code = code;
}
}
六、需要一个验证码生成的工具,创建ValidateCodeGenerator验证码生成接口
public interface ValidateCodeGenerator {

ValidateCode generator(ServletWebRequest request);
}
七、创建ImageCodeGenerator实现ValidateCodeGenerator接口(实现内容不必深究,百度一堆一堆的,大公司也有自己的内部生成方式)
其中的图片长、宽,验证码长度等,都从SecurtiyProperties中获取(可以在application.properties中配置),例:zeke.security.code.image.length = 4
zeke.security.code.image.width = 100
public class ImageCodeGenerator implements ValidateCodeGenerator {

@Autowired
private SecurityProperties securityProperties;

@Override
public ImageCode generator(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 x1 = random.nextInt(12);
int y1 = random.nextInt(12);
g.drawLine(x, y, x+x1, y+y1);
}

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());
}

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,把ImageCodeGenerator注入到Spring容器中@Configuration
public class ValidateCodeBeanConfig {

@Autowired
private SecurityProperties securityProperties;

/**
* 如果要更换图形验证码的实现,可以到DemoImageCodeGenerator中实现(加上@Component("imageCodeGenerator")注解即可)
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator")
public ValidateCodeGenerator imageCodeGenerator(){
ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();
imageCodeGenerator.setSecurityProperties(securityProperties);
return imageCodeGenerator;
}
}
九、创建图形验证码接口 ValidateCodeController@RestController
public class ValidateCodeController {

public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";

private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

@Autowired
private ValidateCodeGenerator ImageCodeGenerator;

/**
* 图形验证码生成、保存、发送
* @param request
* @param response
* @throws IOException
*/
@GetMapping("/code/image")
public void createImageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageCode imageCode = (ImageCode) ImageCodeGenerator.generator(new ServletWebRequest(request));
sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
}
}
十、自定义一个简单的验证码异常public class ValidateCodeException extends AuthenticationException {

public ValidateCodeException(String explanation) {
super(explanation);
}
}
十一、创建一个验证码过滤器,对指定URL进行过滤,验证码错误抛出异常,验证码正确则移除session中保存的验证码
该url也可以在application.properties指定(用逗号分割):zeke.security.code.image.url = /user/*,/userValidateCodeFilter:
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;

private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

/**
* 存放所有需要拦截的URL
*/
private Set<String> urls = new HashSet<>();

private SecurityProperties securityProperties;

private AntPathMat
b372
cher pathMatcher = new AntPathMatcher();

@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(),",");
for (String configUrl : configUrls) {
urls.add(configUrl);
}
urls.add("/authentication/form");
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

boolean action = false;
for (String url : urls) {
if (pathMatcher.match(url,request.getRequestURI())){
action = true;
}
}

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);
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;
}
十二、更改zeke-login.html中的表单,增加图形验证码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login Page</title>
</head>
<body>
<form action="/authentication/form" 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"/>
</td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
十三、在BrowserSecurityConfig中把 /code/image加入放行url ,不然页面会拦截/code/image的请求;
          配置ValidateCodeFilter;



十四、启动项目访问localhost/zeke-login.html 测试
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息