博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springboot验证码重构
阅读量:7100 次
发布时间:2019-06-28

本文共 12560 字,大约阅读时间需要 41 分钟。

考虑到有多种验证机制(例如:常见的图片验证码,手机短信验证码,邮箱验证码)

所以在项目中对验证码进行代码重构,使之更加的具有可扩展性,低耦合性,此项目基于springboot

1.首先Controller层

@RestControllerpublic class ValidateCodeController {    @Autowired    private ValidateCodeProcessorHolder holder;    @GetMapping(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/{type}")    public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {        holder.findValidateCodeProcessor(type).create(request,response);    }}

前端发起类似 /code/image这样的请求,将验证码类型获取到,从hold中找到哪个验证码处理器来进行处理

2.验证码管家

package club.wenfan.youtube.validate;import club.wenfan.youtube.validate.exception.ValidateCodeException;import club.wenfan.youtube.validate.processor.ValidateCodeProcessor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.Map;/** * @author:wenfan * @description: * @data: 2019/1/22 9:31 */@Componentpublic class ValidateCodeProcessorHolder {    @Autowired    private Map
validateCodeProcessors; private Logger log = LoggerFactory.getLogger(getClass()); public ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type){ return findValidateCodeProcessor(type.toString().toLowerCase()); } public ValidateCodeProcessor findValidateCodeProcessor(String type){ String name=type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName(); ValidateCodeProcessor processor=validateCodeProcessors.get(name); //通过类型查找出用那个验证码处理器 log.info("验证码处理器"+name); if(processor == null){ throw new ValidateCodeException("验证码处理器"+name+"不存在"); } return processor; }}
ValidateCodeProcessorHolder类

特别说明一下

@Autowiredprivate Map
validateCodeProcessors;
采用这样的Map Bean注入方式,注入时将所有Bean的名字和类型作为Map注入进来 然后选择时通过Bean的名字来确定用哪个验证码处理器来完成。
3.ValidateCodeProcessor接口
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * @author:wenfan * @description: * @data: 2019/1/21 12:03 */public interface ValidateCodeProcessor {    /**     *  验证码放入session时的前缀     * @author wenfan     * @date     * @param     * @return     */    String SESSION_KEY_PREFIX="SESSION_KEY_FOR_CODE_";    /**     *  创建校验码     * @author wenfan     */    void create(HttpServletRequest request, HttpServletResponse response) throws Exception;    void validate(HttpServletRequest request,HttpServletResponse response);}

4.将验证码处理器的生成、保存、验证、验证抽象出来,单独将发送写成一个抽象方法,用具体的验证码处理来实现此方法

import club.wenfan.youtube.validate.ValidateCodeType;import club.wenfan.youtube.validate.code.ValidateCode;import club.wenfan.youtube.validate.code.ValidateCodeGenerator;import club.wenfan.youtube.validate.exception.ValidateCodeException;import club.wenfan.youtube.validate.processor.ValidateCodeProcessor;import org.apache.commons.lang.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.ServletRequestBindingException;import org.springframework.web.bind.ServletRequestUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.Map;/** * * @author wenfan * @date * @param * @return */public abstract class AbstractValidateCodeProcessor
implements ValidateCodeProcessor { @Autowired private Map
validateCodeGenerators; /** * * @author wenfan * @date * @param * @return */ @Override public void create(HttpServletRequest request, HttpServletResponse response) throws Exception { C validateCode = generate(request); System.out.println(request.getRequestURI()); System.out.println(validateCode.getCode()); save(request, validateCode); send(request,response, validateCode); } /** * 生成校验码 * * @param request * @return */ @SuppressWarnings("unchecked") private C generate(HttpServletRequest request) { String type = getValidateCodeType(request).toString().toLowerCase(); String generatorName = type + ValidateCodeGenerator.class.getSimpleName(); ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName); if (validateCodeGenerator == null) { throw new ValidateCodeException("验证码生成器" + generatorName + "不存在"); } return (C) validateCodeGenerator.CreateCode(request); } /** * 保存校验码 * * @param request * @param validateCode */ private void save(HttpServletRequest request, C validateCode) { ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime()); System.out.println(getSessionKey(request)); request.getSession(true).setAttribute(getSessionKey(request), code); System.out.println(request.getSession().getAttribute(getSessionKey(request))); } /** * 构建验证码放入session时的key * * @param request * @return */ private String getSessionKey(HttpServletRequest request) { return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase(); } /** * 发送校验码,由子类实现 * * @param request * @param validateCode * @throws Exception */ protected abstract void send(HttpServletRequest request,HttpServletResponse response, C validateCode) throws Exception; /** * 根据请求的url获取校验码的类型 * * @param request * @return */ private ValidateCodeType getValidateCodeType(HttpServletRequest request) { String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor"); return ValidateCodeType.valueOf(type.toUpperCase()); } @SuppressWarnings("unchecked") @Override public void validate(HttpServletRequest request,HttpServletResponse response) { ValidateCodeType processorType = getValidateCodeType(request); String sessionKey = getSessionKey(request); System.out.println("sessionKey="+sessionKey); C codeInSession = (C) request.getSession(false).getAttribute(sessionKey); System.out.println(codeInSession==null?"codeinsession为null":"codeinsession不为null"); String codeInRequest; try { codeInRequest = ServletRequestUtils.getStringParameter(request, processorType.getParamNameOnValidate()); } catch (ServletRequestBindingException e) { throw new ValidateCodeException("获取验证码的值失败"); } if (StringUtils.isBlank(codeInRequest)) { throw new ValidateCodeException(processorType + "验证码的值不能为空"); } if (codeInSession == null) { throw new ValidateCodeException(processorType + "验证码不存在"); } if (codeInSession.isExpired()) { request.getSession().removeAttribute(sessionKey); throw new ValidateCodeException(processorType + "验证码已过期"); } if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) { throw new ValidateCodeException(processorType + "验证码不匹配"); } request.getSession().removeAttribute(sessionKey); }}
AbstractValidateCodeProcessor

5.具体的验证码生成器只有发送方法,这里只贴了图片验证码处理器

@Component("imageValidateCodeProcessor")public class ImageCodeProcessor extends AbstractValidateCodeProcessor
{ @Override protected void send(HttpServletRequest request, HttpServletResponse response, ImgCode imgCode) throws Exception { ImageIO.write(imgCode.getImage(),"JPEG",response.getOutputStream()); }}

6.在处理器抽象法中也运用方法Map Bean 的方式来选择验证码生成器的类型

public interface ValidateCodeGenerator {    ValidateCode CreateCode(HttpServletRequest request);}

7.具体的验证码生成器

import club.wenfan.youtube.properties.SecurityProperties;import club.wenfan.youtube.validate.code.ImgCode;import club.wenfan.youtube.validate.code.ValidateCodeGenerator;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.bind.ServletRequestUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.awt.*;import java.awt.image.BufferedImage;import java.util.Random;/** * @author:wenfan * @description: * @data: 2019/1/1 18:33 */@Component("imageValidateCodeGenerator")public class ImgCodeGenerator implements ValidateCodeGenerator {    @Autowired    private SecurityProperties securityProperties;    private Logger log = LoggerFactory.getLogger(getClass());    @Override    public ImgCode CreateCode(HttpServletRequest request) {        //可以在请求中加入 width/height get参数  当没有参数时从用户的自定义的配置文件中读取        int width =ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImg().getWidth());// 定义图片的width        int height = ServletRequestUtils.getIntParameter(request," height",securityProperties.getCode().getImg().getHeight());// 定义图片的height        int codeCount =securityProperties.getCode().getImg().getCodeCount();// 定义图片上显示验证码的个数        int expiredTime = securityProperties.getCode().getImg(). getExpiredTime();        int xx = 18;        int fontHeight = 20;        int codeY = 27;        char[] codeSequence = { '0','1', '2', '3', '4','5', '6', '7', '8', '9' };        // 定义图像buffer        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);        Graphics gd = buffImg.getGraphics();        // 创建一个随机数生成器类        Random random = new Random();        // 将图像填充为白色        gd.setColor(Color.WHITE);        gd.fillRect(0, 0, width, height);        // 创建字体,字体的大小应该根据图片的高度来定。        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);        // 设置字体。        gd.setFont(font);        // 画边框。        gd.setColor(Color.BLACK);        gd.drawRect(0, 0, width - 1, height - 1);        // 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。        gd.setColor(Color.BLACK);        for (int i = 0; i < 30; i++) {            int x = random.nextInt(width);            int y = random.nextInt(height);            int xl = random.nextInt(12);            int yl = random.nextInt(12);            gd.drawLine(x, y, x + xl, y + yl);        }        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。        StringBuffer randomCode = new StringBuffer();        int red = 0, green = 0, blue = 0;        // 随机产生codeCount数字的验证码。        for (int i = 0; i 
View Code

8.如果想更换系统中默认的验证码处理器可以采用一种可扩展的bean注入方式

@Configurationpublic class ValidateCodeBeanConfig {    @Autowired    private SecurityProperties securityProperties;    /**     *  当容器中没有imageValidateCodeGenerator 这个Bean的时候,会主动配置下面的默认Bean     *  以增量的形式实现变化不     *     * @author wenfan     * @date     * @param     * @return     */    @Bean    @ConditionalOnMissingBean(name = "imageValidateCodeGenerator")    public ValidateCodeGenerator imgCodeGenerator(){        ImgCodeGenerator codeGenerator=new ImgCodeGenerator();        codeGenerator.setSecurityProperties(securityProperties);        return codeGenerator;    }    /**     *  class形式和 name的方式相同     * @author wenfan     * @date     * @param     * @return     */    @Bean    @ConditionalOnMissingBean(SmsCodeSender.class)    public SmsCodeSender smsCodeSender(){        return new SmsCodeSenderImpl();    }}

如果不想用默认验证码生成器,之需要注册bean,bean名字为imageValidateCodeGenerator

9.验证码过滤

写一个过滤器继承OncePerRequestFilter

@Override    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {        response.setContentType("application/json;charset=utf-8");        ValidateCodeType type=getValidateCodeType(request);        if(type !=null){            logger.info("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + type);            try {                validateCodeProcessorHolder.findValidateCodeProcessor(type).validate(request,response);                logger.info("验证码通过");            } catch (ValidateCodeException e) {                authenticationFailureHandler.onAuthenticationFailure(request,response,e);                return;            }        }            filterChain.doFilter(request,response);    }

发起请求时,验证码处理器管家来决定哪一个处理器来处理

10.将过滤器添加到配置中

@Component("validateCodeSecurityConfig")public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter
{ @Autowired private Filter vaildateCodeFilter; @Override public void configure(HttpSecurity http) throws Exception { http.addFilterBefore(vaildateCodeFilter,AbstractPreAuthenticatedProcessingFilter.class); }}

 

感谢阅读

 

转载于:https://www.cnblogs.com/outxiao/p/10409779.html

你可能感兴趣的文章
HTML 中的 dl(dt,dd)、ul(li)、ol(li)
查看>>
Linux下Redis主从复制以及SSDB主主复制环境部署记录
查看>>
如何让win10实现关机确认-暂没确认
查看>>
常用js函数整理--common.js
查看>>
java内存泄漏与内存溢出
查看>>
互联网服务器的实现过程需要考虑哪些安全问题 & 加解密及哈希知识点
查看>>
sql server2008给数据表,字段,添加修改注释
查看>>
meta标签清理缓存
查看>>
onvif开发之设备发现功能的实现--转
查看>>
虚拟机下linux迁移造成MAC地址异常处理办法
查看>>
数据库事务原子性、一致性是怎样实现的?[转]
查看>>
“营改增”后你该知道的…代开发票需要知道的16个事项
查看>>
动态设置js的属性
查看>>
Fragment的setUserVisibleHint方法实现懒加载,但setUserVisibleHint 不起作用?
查看>>
lodash(二)对象+循环遍历+排序
查看>>
Java -- 获取MAC地址
查看>>
URL中的#
查看>>
CentOS自带mysql配置(密码更改、端口开放访问、添加进系统启动项)
查看>>
MYSQL中动态行数据转列数据
查看>>
anchor_target_layer中的bounding regression
查看>>