博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Security 图形验证码校验
阅读量:4186 次
发布时间:2019-05-26

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

验证码校验也是通过Spring Security中的过滤器链来进行校验的。

文章目录

编写图形验证码服务

1、创建 ImageCode 实体类

该实体类用于存储验证码的相关数据。

@Datapublic class ImageCode {    /**     *  图形验证码     */    private BufferedImage imageCode;    /**     *  验证码     */    private String code;    /**     *  过期时间     */    private LocalDateTime expireTime;    public ImageCode(BufferedImage imageCode, String code, int expireTime) {        this.imageCode = imageCode;        this.code = code;        this.expireTime = LocalDateTime.now().plusSeconds(expireTime);    }    public ImageCode(BufferedImage imageCode, String code, LocalDateTime expireTime) {        this.imageCode = imageCode;        this.code = code;        this.expireTime = expireTime;    }    public boolean isExpire() {        return LocalDateTime.now().compareTo(this.expireTime) > 0;    }}
2、创建验证码服务
@RestController@RequestMappingpublic class ValidateCodeController {    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();    /** ImageCode在session中的key */    public static final String SESSION_KEY_IMAGE_CODE = "SESSION_KEY_IMAGE_CODE";    @GetMapping("/code/image")    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {        // 生成imageCode        ImageCode imageCode = createImageCode(request);        // 将imageCode 保存在session中        sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_IMAGE_CODE, imageCode);        ImageIO.write(imageCode.getImageCode(), "JPEG", response.getOutputStream());    }    public ImageCode createImageCode(HttpServletRequest request) {        // 在内存中创建图像        int width = 65, height = 20;        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);        Graphics graphics = bufferedImage.getGraphics();        // 设定背景色        graphics.setColor(getRandColor(230, 255));        graphics.fillRect(0, 0, 100, 25);        // 设定字体        graphics.setFont(new Font("Arial", Font.CENTER_BASELINE | Font.ITALIC, 18));        // 产生0条干扰线,        graphics.drawLine(0, 0, 0, 0);        // 随机产生四位验证码        String sRand = "";        Random random = new Random();        for (int i = 0; i < 4; i++) {            String rand = String.valueOf(random.nextInt(10));            sRand += rand;            // 将认证码显示到图象中            graphics.setColor(getRandColor(100, 150));// 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成            graphics.drawString(rand, 15 * i + 6, 16);        }        graphics.dispose();        return new ImageCode(bufferedImage, sRand, 60);    }    /**     * 给定范围获得随机颜色     *     * @param fc     * @param bc     * @return     */    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);    }}
3、在 Security 的配置文件中,将获取验证码的请求,进行认证过滤。
.antMatchers("/code/image","/authentication/require",securityProperties.getBrowser().getLoginpage()).permitAl()
4、登录界面的html代码
用户名:
密码:
图形验证码:

验证码校验

通过Filter实现,验证码的校验逻辑。自定义的验证码过滤器,前置到UsernamePasswordAuthenticationFilter之前。

1、自定义验证码校验过滤器
public class ValidateCodeFilter  extends OncePerRequestFilter {    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();    private AuthenticationFailureHandler authenticationFailureHandler;    @Override    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {        if("/authentication/form".equals(httpServletRequest.getRequestURI())          && "POST".equalsIgnoreCase(httpServletRequest.getMethod())) {            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, ValidateCodeException {        ImageCode imageCodeSession = (ImageCode)sessionStrategy.getAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY_IMAGE_CODE);        String code = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(),"imageCode");        if (StrUtil.isBlank(code)) {            throw new ValidateCodeException("验证码的值不能为空");        }        if (imageCodeSession == null) {            throw new ValidateCodeException("验证码不存在");        }        if (imageCodeSession.isExpire()) {            sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY_IMAGE_CODE);            throw new ValidateCodeException("验证码已过期");        }        if (!StrUtil.equals(imageCodeSession.getCode(), code)) {            throw new ValidateCodeException("验证码不匹配");        }        sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY_IMAGE_CODE);    }    public AuthenticationFailureHandler getAuthenticationFailureHandler() {        return authenticationFailureHandler;    }    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {        this.authenticationFailureHandler = authenticationFailureHandler;    }}
2、自定义的异常类
public class ValidateCodeException extends AuthenticationException {    public ValidateCodeException(String msg) {        super(msg);    }}
3、配置验证码校验过滤器
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private SecurityProperties securityProperties;    @Autowired    private LoginSuccessHandler loginSuccessHandler;    @Autowired    private LoginFailureHandler loginFailureHandler;    /**     *  为减少代码重复开发,多个应用使用同一个认证中心,每个应用需要自己指定登录页面。     *  这里需要将 loginpage 指向一个controlelr地址。     *  如果是html页面,就跳转到指定的登录页。     *  如果不是html页面,就提示401 没有认证信息。     *  如果有应用有指定的就使用自己的。如果没指定就使用本认证模块默认的登录页。     * @param http     * @throws Exception     */    @Override    protected void configure(HttpSecurity http) throws Exception {    	// 引入验证码过滤器        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);        // 配置过滤器的位置        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);        http.formLogin()                .loginPage("/authentication/require")                .loginProcessingUrl("/authentication/form")                .successHandler(loginSuccessHandler)                .failureHandler(loginFailureHandler)                .successForwardUrl("/index")//                .defaultSuccessUrl("/index")                .and()                .authorizeRequests()                .antMatchers("/code/image","/authentication/require",securityProperties.getBrowser().getLoginpage()).permitAll()                .anyRequest().authenticated()                .and().csrf().disable();    }     }

验证码代码重构

重构代码将一些参数,改为可配置的,降低代码耦合度; 在这个Spring Security的案例中,是把当前应用作为可以重用的应用架构,进而可以引入到其他应用中去,减少用户认证的重复开发。每个应用可能可能自己的验证码生成逻辑不同,可以通过代码重构,让第三方应用实现自己的生成验证码的逻辑。

验证码基本参数配置

应用级别配置:配置在引用的第三方项目中

默认级别配置:给配置值指定设置默认值

1、验证码参数配置如下:

security:  browser:    logintype: html  validate_code:    image:      width: 80      height: 40      length: 5      expire_time: 100

2、对应的配置实体类

@ConfigurationProperties(prefix = "security")public class SecurityProperties {    private BrowserProperties browser = new BrowserProperties();    private ValidateCodeProperties validateCode = new ValidateCodeProperties();  // 省略getter和setter方法}
@Datapublic class ValidateCodeProperties {    ImageCodeProperties image = new ImageCodeProperties();}
@Datapublic class ImageCodeProperties {    private int width = 67;    private int height = 23;    private int length = 4; // 验证码长度    private int expireTime = 60; // 过期时间}

3、在ValidateCodeController中,将对应的验证码参数改为从配置类中读取。

public ImageCode createImageCode(HttpServletRequest request) {        ImageCodeProperties image = securityProperties.getValidateCode().getImage();        // 在内存中创建图像//        int width = 65, height = 20;        int width = ServletRequestUtils.getIntParameter(request, "width", image.getWidth());        int height = ServletRequestUtils.getIntParameter(request, "height", image.getHeight());        int length = ServletRequestUtils.getIntParameter(request, "length", image.getLength());        int expireTime = ServletRequestUtils.getIntParameter(request, "expireTime", image.getExpireTime());        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);        Graphics graphics = bufferedImage.getGraphics();        // 设定背景色        graphics.setColor(getRandColor(230, 255));        graphics.fillRect(0, 0, 100, 25);        // 设定字体        graphics.setFont(new Font("Arial", Font.CENTER_BASELINE | Font.ITALIC, 18));        // 产生0条干扰线,        graphics.drawLine(0, 0, 0, 0);        // 随机产生四位验证码        String sRand = "";        Random random = new Random();        for (int i = 0; i < length; i++) {            String rand = String.valueOf(random.nextInt(10));            sRand += rand;            // 将认证码显示到图象中            graphics.setColor(getRandColor(100, 150));// 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成            graphics.drawString(rand, 15 * i + 6, 16);        }        graphics.dispose();        return new ImageCode(bufferedImage, sRand, expireTime);    }
验证码拦截的接口可配置

如果 第三方应用认证的接口和默认的路径不一样呢,这时候就需要改为可配置的了。

在上面的配置类中添加url属性。

在验证码校验过滤器的 ValidateCodeFilter类中,实现 接口 InitializingBean的方法afterPropertiesSet()

实现了 InitializingBean接口的类,实例化bean时,会自动执行,afterPropertiesSet()方法。

public class ValidateCodeFilter  extends OncePerRequestFilter implements InitializingBean {    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();    private AuthenticationFailureHandler authenticationFailureHandler;    private Set
urls; // 存储需要拦截的url private SecurityProperties securityProperties; @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); String url = securityProperties.getValidateCode().getImage().getUrl(); String[] configUrl = url.split(","); urls = Stream.of(configUrl).collect(Collectors.toSet()); // 添加默认的地址 urls.add("/authentication/form"); } /** * 重构 * @param httpServletRequest * @param httpServletResponse * @param filterChain * @throws ServletException * @throws IOException */ @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { boolean action = false; for (String url : urls) { if (url.equals(httpServletRequest.getRequestURI())) { action = true; break; } } if (action) { try { validate(new ServletWebRequest(httpServletRequest)); } catch (ValidateCodeException e) { // 调用失败处理器 authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e); return; } } filterChain.doFilter(httpServletRequest, httpServletResponse); }}

ValidateCodeFilter 类没有通过Spring Bean的方式注入,还是需要在SecurityConfig中进行手动调用 afterPropertiesSet()

protected void configure(HttpSecurity http) throws Exception {        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);        validateCodeFilter.setSecurityProperties(securityProperties);        validateCodeFilter.afterPropertiesSet();        // 配置过滤器的位置        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);        http.formLogin()                .loginPage("/authentication/require")                .loginProcessingUrl("/authentication/form")                .successHandler(loginSuccessHandler)                .failureHandler(loginFailureHandler)                .successForwardUrl("/index")//                .defaultSuccessUrl("/index")                .and()                .authorizeRequests()                .antMatchers("/code/image","/authentication/require",securityProperties.getBrowser().getLoginpage()).permitAll()                .anyRequest().authenticated()                .and().csrf().disable();    }
验证码实现逻辑可配

思路: 逻辑可配,就是抽象成接口,实现由客户端实现。

1、 定义生成验证码逻辑的接口

/** * @Author L.jg * @Title       抽象接口,让客户端可配置接口 * @Date 2021/5/24 11:42 */public interface ValidateCodeGenerate {    ImageCode generate(HttpServletRequest request);}

2、实现接口,自定义验证码的实现逻辑

public class ImageCodeGenerate implements  ValidateCodeGenerate {    private ImageCodeProperties imageCodeProperties;    public ImageCodeGenerate(ImageCodeProperties imageCodeProperties) {        this.imageCodeProperties = imageCodeProperties;    }    @Override    public ImageCode generate(HttpServletRequest request) {        int width = ServletRequestUtils.getIntParameter(request, "width", imageCodeProperties.getWidth());        int height = ServletRequestUtils.getIntParameter(request, "height", imageCodeProperties.getHeight());        int length = ServletRequestUtils.getIntParameter(request, "length", imageCodeProperties.getLength());        int expireTime = ServletRequestUtils.getIntParameter(request, "expireTime", imageCodeProperties.getExpireTime());        return createImageCode(width, height, length, expireTime);    }    public ImageCode createImageCode(int width, int height, int length, int expireTime) {        // 在内存中创建图像        // int width = 65, height = 20;        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);        Graphics graphics = bufferedImage.getGraphics();        // 设定背景色        graphics.setColor(getRandColor(230, 255));        graphics.fillRect(0, 0, 100, 25);        // 设定字体        graphics.setFont(new Font("Arial", Font.CENTER_BASELINE | Font.ITALIC, 18));        // 产生0条干扰线,        graphics.drawLine(0, 0, 0, 0);        // 随机产生四位验证码        String sRand = "";        Random random = new Random();        for (int i = 0; i < length; i++) {            String rand = String.valueOf(random.nextInt(10));            sRand += rand;            // 将认证码显示到图象中            graphics.setColor(getRandColor(100, 150));// 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成            graphics.drawString(rand, 15 * i + 6, 16);        }        graphics.dispose();        return new ImageCode(bufferedImage, sRand, expireTime);    }    /**     * 给定范围获得随机颜色     *     * @param fc     * @param bc     * @return     */    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);    }}

3、 在Spring boot中注入Bean

@Configurationpublic class VlidateCodeConfig {    @Autowired    private SecurityProperties securityProperties;    @Bean    // 如果Spring 容易中存在 imageCodeGenerate 的bean就不会再初始化该bean了    @ConditionalOnMissingBean(name = "imageCodeGenerate")    public ValidateCodeGenerate imageCodeGenerate() {        ImageCodeGenerate imageCodeGenerate = new ImageCodeGenerate(securityProperties.getValidateCode().getImage());        return imageCodeGenerate;    }}

4、 在验证码controller中,注入验证码实现类

@RestController@RequestMappingpublic class ValidateCodeController {    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();    @Autowired    private SecurityProperties securityProperties;        @Autowired    private ValidateCodeGenerate validateCodeGenerate;    /** ImageCode在session中的key */    public static final String SESSION_KEY_IMAGE_CODE = "SESSION_KEY_IMAGE_CODE";    @GetMapping("/code/image")    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {        // 生成imageCode//        ImageCode imageCode = createImageCode(request);        ImageCode imageCode = validateCodeGenerate.generate(request);        // 将imageCode 保存在session中        sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_IMAGE_CODE, imageCode);        ImageIO.write(imageCode.getImageCode(), "JPEG", response.getOutputStream());    }    }

转载地址:http://udfoi.baihongyu.com/

你可能感兴趣的文章
Solr作为一个Web应用,可以部署在多种应用服务器
查看>>
Directory家族的层级分布图
查看>>
我们为什么应该坚持写博客
查看>>
因为多个jar可能记录日志信息时,日志模块,不知道需要用那个jar包
查看>>
Lucene里面支持join操作
查看>>
solr 4.2的入门配置
查看>>
shell脚本一键安装solr
查看>>
solr原子更新功能
查看>>
greenplum + pgsql和Hadoop+hive+hbase
查看>>
cpu,硬盘,内存
查看>>
Elasticsearch 合理内存分配
查看>>
elasticsearch 与 hive集成
查看>>
ElasticSearch 2 的节点调优(ElasticSearch性能)
查看>>
Elasticsearch与hadoop比较
查看>>
raid5
查看>>
eclipse中调试solr
查看>>
solr有关Pig0.12.0和Solr4.10.2
查看>>
建议使用Solr或ElasticSearch这样的封装了
查看>>
图形数据库Neo4J简介
查看>>
JDBC 连接 带实例名的SQL Server
查看>>