Spring Security基于自定义的认证提供器实现图形验证码流程解析

目录
  • 前言
  • 一. 认证提供器简介
    • 1. 认证提供器AuthenticationProver
    • 2. WebAuthenticationDetails类介绍
  • 二. 实现图形验证码
    • 1. 添加依赖包
    • 2. 创建Producer对象
    • 3. 创建生成验证码的接口
    • 4. 自定义异常
    • 5. 自定义WebAuthenticationDetails
    • 6. 自定义AuthenticationDetailsSource
    • 7. 自定义DaoAuthenticationProver
    • 8. 添加SecurityConfig
    • 9. 编写测试页面
    • 10. 代码结构
    • 11. 启动项目测试

前言

在上一个章节中,一一哥 带大家实现了如何在Spring Security中添加执行自定义的过滤器,进而实现验证码校验功能这种实现方式,只是实现验证码功能的方式之一,接下来我们再学习另一种实现方式,就是利用AuthenticationProver来实现验证码功能,通过这个案例,我们学习如何进行自定义AuthenticationProver。

一. 认证提供器简介

在上一章节中,我带各位利用自定义的过滤器实现了图形验证码效果,接下来我们利用另一种方式,基于自定义的认证提供器来实现图形验证码。

1. 认证提供器AuthenticationProver

在第11章节中,壹哥 给大家讲过Spring Security的认证授权实现流程,其中就给大家讲解过AuthenticationProver的作用,接下来我们看一下AuthenticationProver接口的类关系图:

从上图中可知,AuthenticationProver是一个接口,该接口有一个直接的子类AbstractUserDetailsAuthenticationProver,该类有2个抽象的方法:additionalAuthenticationChecks() 和 retrieveUser(),如下图:

我们可以通过编写一个子类继承AbstractUserDetailsAuthenticationProver,复写这2个抽象方法,进行满足自己需求的扩展实现。Spring Security中的DaoAuthenticationProver子类就是通过复写这2个抽象方法,实现了基于数据库模型的认证授权。

我们今天会通过继承DaoAuthenticationProver,来实现图形验证码的校验功能。

2. WebAuthenticationDetails类介绍

了解完上面的AuthenticationProver类之后,我们还需要了解另一个类WebAuthenticationDetails。

我们知道在Spring Security中有一个UsernamePasswordAuthenticationToken类,封装了用户的principal、credentials信息,该类还从它的父类AbstractAuthenticationToken中继承了details信息。其中这个details信息表示认证用户的额外信息,比如请求用户的remoteAddress和sessionId等信息,这两个信息都是在另一个WebAuthenticationDetails类中定义的,所以我们可以利用WebAuthenticationDetails来封装用户的额外信息。

了解完上面的这些必要的API,我们就可以实现今天的需求了。

二. 实现图形验证码

1. 添加依赖包

我们还是和之前的案例一样,可以先创建一个新的module,创建过程略。

在本案例中我们依然采用github上的开源验证码解决方案kaptcha,所以需要在原有项目的基础上添加kaptcha的依赖包。

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2. 创建Producer对象

跟上一个案例一样,创建CaptchaConfig配置类,在该类中创建一个Producer对象,对验证码对象进行必要的配置。

@Configuration
public class CaptchaConfig {

    @Bean
    public Producer captcha() {
        // 配置图形验证码的基本参数
        Properties properties = new Properties();
        // 图片宽度
        properties.setProperty("kaptcha.image.wth", "150");
        // 图片长度
        properties.setProperty("kaptcha.image.height", "50");
        // 字符集
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
        // 字符长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        Config config = new Config(properties);
        // 使用默认的图形验证码实现,当然也可以自定义实现
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

}

3. 创建生成验证码的接口

在上面创建了Producer对象后,接着创建一个生成验证码的接口,该接口中负责生成验证码图片,并将验证码存储到session中。

@Controller
public class CaptchaController {

    @Autowired
    private Producer captchaProducer;

    @GetMapping("/captcha.jpg")
    public vo getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 设置内容类型
        response.setContentType("image/jpeg");
        // 创建验证码文本
        String capText = captchaProducer.createText();

        // 将验证码文本设置到session
        request.getSession().setAttribute("captcha", capText);

        // 创建验证码图片
        BufferedImage bi = captchaProducer.createImage(capText);
        // 获取响应输出流
        ServletOutputStream out = response.getOutputStream();
        // 将图片验证码数据写到响应输出流
        ImageIO.write(bi, "jpg", out);

        // 推送并关闭响应输出流
        try {
            out.flush();
        } finally {
            out.close();
        }
    }

}

4. 自定义异常

接下来自定义一个运行时异常,用于处理验证码校验失败时抛出异常提示信息。

public class VerificationCodeException extends AuthenticationException {

    public VerificationCodeException() {
        super("图形验证码校验失败");
    }

}

5. 自定义WebAuthenticationDetails

我在上面给大家介绍过WebAuthenticationDetails这个类,知道该类中可以封装用户的额外信息,所以在这里我们自定义一个WebAuthenticationDetails类,封装验证码信息,并把用户传递过来的验证码与session中保存的验证码进行对比。

/**
 * 添加额外的用户认证信息
 */
public class MyWebAuthenticationDetails extends WebAuthenticationDetails {

    private String imageCode;

    private String savedImageCode;

    public String getImageCode() {
        return imageCode;
    }

    public String getSavedImageCode() {
        return savedImageCode;
    }

    /**
     * 补充用户提交的验证码和session保存的验证码
     */
    public MyWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        this.imageCode = request.getParameter("captcha");
        //获取session对象
        HttpSession session = request.getSession();

        this.savedImageCode = (String) session.getAttribute("captcha");
        if (!StringUtils.isEmpty(this.savedImageCode)) {
            // 随手清除验证码,不管是失败还是成功,所以客户端应在登录失败时刷新验证码
            session.removeAttribute("captcha");
        }
    }

}

6. 自定义AuthenticationDetailsSource

AuthenticationDetailsSource是一个接口,该接口带有一个buildDetails方法,该方法会在创建一个新的authentication的details对象时被调用,而且可以在这里传递给details对象一个request参数,如下图所示:

所以这里我们定义一个AuthenticationDetailsSource类,通过该类构建出上面定义的WebAuthenticationDetails对象,并且给WebAuthenticationDetails传递进去HttpServletRequest对象。

@Component
public class MyWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest,WebAuthenticationDetails> {

    /**
     * 创建一个WebAuthenticationDetails对象
     */
    @Overre
    public WebAuthenticationDetails buildDetails(HttpServletRequest request) {

        return new MyWebAuthenticationDetails(request);
    }

}

7. 自定义DaoAuthenticationProver

接下来通过继承DaoAuthenticationProver父类,来引入对图形验证码的验证操作。

/**
 * 在常规的数据库认证之上,添加图形验证码功能
 */
@Component
public class MyAuthenticationProver extends DaoAuthenticationProver {

    /**
     * 构造方法注入UserDetailService和PasswordEncoder
     */
    public MyAuthenticationProver(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.setUserDetailsService(userDetailsService);
        this.setPasswordEncoder(passwordEncoder);
    }

    /**
     * 在常规的认证之上,添加额外的图形验证码功能
     */
    @Overre
    protected vo additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        //获取token令牌中关联的details对象,并将其转换为我们自定义的MyWebAuthenticationDetails
        MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) usernamePasswordAuthenticationToken.getDetails();
        String imageCode = details.getImageCode();
        String savedImageCode = details.getSavedImageCode();

        // 检验图形验证码
        if (StringUtils.isEmpty(imageCode) || StringUtils.isEmpty(savedImageCode) || !imageCode.equals(savedImageCode)) {
            throw new VerificationCodeException();
        }

        //在正常的认证检查之前,添加额外的关于图形验证码的校验
        super.additionalAuthenticationChecks(userDetails, usernamePasswordAuthenticationToken);
    }

}

8. 添加SecurityConfig

然后创建编写SecurityConfig类,关联配置我们前面编写的AuthenticationDetailsSource和AuthenticationProver类。

@SuppressWarnings("all")
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myWebAuthenticationDetailsSource;

    @Autowired
    private AuthenticationProver authenticationProver;

    @Overre
    protected vo configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/api/**")
                .hasRole("ADMIN")
                .antMatchers("/user/api/**")
                .hasRole("USER")
                .antMatchers("/app/api/**", "/captcha.jpg")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
            	//这里关联配置自定义的AuthenticationDetailsSource
                .authenticationDetailsSource(myWebAuthenticationDetailsSource)
                .failureHandler(new SecurityAuthenticationFailureHandler())
                .successHandler(new SecurityAuthenticationSuccessHandler())
                .loginPage("/myLogin.html")
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }

    //在这里关联我们自定义的AuthenticationProver
    @Overre
    protected vo configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProver(authenticationProver);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {

        return NoOpPasswordEncoder.getInstance();
    }

}

9. 编写测试页面

最后编写一个自定义的登录页面,在这里添加对验证码接口的引用,我这里列出html的核心代码。

<body>
        <div class="login">
            <h2>Access Form</h2>
            <div class="login-top">
                <h1>登录验证</h1>
                <form action="/login" method="post">
                    <input type="text" name="username" placeholder="username" />
                    <input type="password" name="password" placeholder="password" />
                    <div style="display: flex;">
                        <!-- 新增图形验证码的输入框 -->
                        <input type="text" name="captcha" placeholder="captcha" />
                        <!-- 图片指向图形验证码API -->
                        <img src="/captcha.jpg" alt="captcha" height="50px" wth="150px" style="margin-left: 20px;">
                    </div>
                    <div class="forgot">
                        <a href="#" rel="external nofollow"  rel="external nofollow" >忘记密码</a>
                        <input type="submit" value="登录" >
                    </div>
                </form>
            </div>
            <div class="login-bottom">
                <h3>新用户&nbsp;<a href="#" rel="external nofollow"  rel="external nofollow" >注&nbsp;册</a></h3>
            </div>
        </div>
    </body>

10. 代码结构

本案例的主要代码结构如下图所示,各位可以参考创建。

11. 启动项目测试

接下来我们启动项目,跳转到登录页面后,我们就可以看到验证码已经被创建出来了。

此时我们可以看到生成的数字验证码,在我们输入正确的用户名、密码、验证码后,就可以成功的登录进去访问web接口了。

至此,我们就实现了基于自定义的认证提供器来实现图形验证码功能了,这种实现方式要比第一种实现方式更复杂一些,其实都能满足我们的开发需求。有的小伙伴会问,开发时到底选择哪一种方式呢?壹哥觉得都无所谓的!你有什么更好的见解吗?可以在评论区留言哦!

到此这篇关于Spring Security基于自定义的认证提供器实现图形验证码的文章就介绍到这了,更多相关Spring Security认证提供器实现图形验证码内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot结合SpringSecurity实现图形验证码功能

    本文介绍了SpringBoot结合SpringSecurity实现图形验证码功能,分享给大家,具体如下: 生成图形验证码 根据随机数生成图片 将随机数存到Session中 将生成的图片写到接口的响应中 生成图形验证码的过程比较简单,和SpringSecurity也没有什么关系.所以就直接贴出代码了 根据随机数生成图片 /** * 生成图形验证码 * @param request * @return */ private ImageCode generate(ServletWebRequest r

  • SpringSecurity实现图形验证码功能实例代码

    Spring Security Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作. 本文重点给大家介绍Spri

  • Spring Security基于过滤器实现图形验证码功能

    目录 前言 一. 验证码简介 二. 基于过滤器实现图形验证码 1. 实现概述 2. 创建新模块 3. 添加依赖包 4. 创建Producer对象 5. 创建生成验证码的接口 6. 自定义异常 7. 创建拦截验证码的过滤器 8. 编写SecurityConfig 9. 编写测试页面 10. 代码结构 11. 启动项目测试 前言 在前两个章节中,一一哥 带大家学习了Spring Security内部关于认证授权的核心API,以及认证授权的执行流程和底层原理.掌握了这些之后,对于Spring Secu

  • Spring Security基于自定义的认证提供器实现图形验证码流程解析

    目录 前言 一. 认证提供器简介 1. 认证提供器AuthenticationProver 2. WebAuthenticationDetails类介绍 二. 实现图形验证码 1. 添加依赖包 2. 创建Producer对象 3. 创建生成验证码的接口 4. 自定义异常 5. 自定义WebAuthenticationDetails 6. 自定义AuthenticationDetailsSource 7. 自定义DaoAuthenticationProver 8. 添加SecurityConfig

  • spring Security的自定义用户认证过程详解

    首先我需要在xml文件中声明.我要进行自定义用户的认证类,也就是我要自己从数据库中进行查询 <http pattern="/*.html" security="none"/> <http pattern="/css/**" security="none"/> <http pattern="/img/**" security="none"/> <h

  • Spring Security基于数据库实现认证过程解析

    创建数据库 SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `nam

  • Spring Security基于散列加密方案实现自动登录功能

    目录 前言 一. 自动登录简介 1. 为什么要自动登录 2. 自动登录的实现方案 二. 基于散列加密方案实现自动登录 1. 配置加密令牌的key 2. 配置SecurityConfig类 3. 添加测试接口 4. 启动项目测试 三. 散列加密方案实现原理 1. cookie的加密原理分析 2. cookie的解码原理分析 3. 自动登录的源码分析 3.1 令牌生成的源码分析 3.2 令牌解析的源码分析 前言 在前面的2个章节中,一一哥 带大家实现了在Spring Security中添加图形验证码

  • Spring Security使用数据库登录认证授权

    目录 一.搭建项目环境 1.创建 RBAC五张表 2.创建项目 二.整合 Spring Security实现用户认证 1.后端整合 2.前端整合 三.整合 Spring Security实现用户授权 1.后端 2.前端 一.搭建项目环境 1.创建 RBAC五张表 RBAC,即基于角色的权限访问控制(Role-Based Access Control),就是用户通过角色与权限进行关联.在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系. 在 MySQL数据库中,创建如下几个表: D

  • Spring Security OAuth 自定义授权方式实现手机验证码

    Spring Security OAuth 默认提供OAuth2.0 的四大基本授权方式(authorization_code\implicit\password\client_credential),除此之外我们也能够自定义授权方式. 先了解一下Spring Security OAuth提供的两个默认 Endpoints,一个是AuthorizationEndpoint,这个是仅用于授权码(authorization_code)和简化(implicit)模式的.另外一个是TokenEndpoi

  • Spring Security 基于URL的权限判断源码解析

    目录 1. FilterSecurityInterceptor 源码阅读 2. 自定义基于url的授权 1. FilterSecurityInterceptor 源码阅读 org.springframework.security.web.access.intercept.FilterSecurityInterceptor 通过过滤器实现对HTTP资源进行安全处理. 该安全拦截器所需的 SecurityMetadataSource 类型为 FilterInvocationSecurityMetad

  • Spring Security实现自定义访问策略

    目录 1.安全注释 2.投票机制 3.配置 4.测验 前言: 我们将探索一个用户共享电子表格的系统,每个电子表格的访问权限单独存储.我们已经尽可能简单地对权限存储进行了显式建模:想象一下,它在调用其他地方的记录系统.请注意,在这个简化的实现中,访问决定是二进制的:要么有访问权,要么没有.在这个实现中,读/写访问权没有区别. 1.安全注释 打开SpreadsheetService会显示一个用@Secured注释的方法. @Secured("com.jdriven.model.Spreadsheet

  • 详解Spring Security的formLogin登录认证模式

    一.formLogin的应用场景 在本专栏之前的文章中,已经给大家介绍过Spring Security的HttpBasic模式,该模式比较简单,只是进行了通过携带Http的Header进行简单的登录验证,而且没有定制的登录页面,所以使用场景比较窄. 对于一个完整的应用系统,与登录验证相关的页面都是高度定制化的,非常美观而且提供多种登录方式.这就需要Spring Security支持我们自己定制登录页面,也就是本文给大家介绍的formLogin模式登录认证模式. 准备工作 新建一个Spring B

随机推荐