Spring Security 登录时添加图形验证码实现实例

目录
  • 前言
  • 验证码生成
    • 加入验证码依赖
    • 验证码配置
    • 验证码接口
  • 加入依赖
    • 基于过滤器
      • 编写自定义认证逻辑
      • 测试
    • 基于认证器
      • 编写自定义认证逻辑
      • 测试

前言

在前面的几篇文章中,登录时都是使用用户名 + 密码进行登录的,但是在实际项目当中,登录时,还需要输入图形验证码。那如何在 Spring Security 现有的认证体系中,加入自己的认证逻辑呢?这就是本文的内容,本文会介绍两种实现方案,一是基于过滤器实现;二是基于认证器实现。

验证码生成

既然需要输入图形验证码,那先来生成验证码吧。

加入验证码依赖

<!--验证码生成器-->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Kaptcha 依赖是谷歌的验证码工具。

验证码配置

@Configuration
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha captchaProducer() {
        Properties properties = new Properties();
        // 是否显示边框
        properties.setProperty("kaptcha.border","yes");
        // 边框颜色
        properties.setProperty("kaptcha.border.color","105,179,90");
        // 字体颜色
        properties.setProperty("kaptcha.textproducer.font.color","blue");
        // 字体大小
        properties.setProperty("kaptcha.textproducer.font.size","35");
        // 图片宽度
        properties.setProperty("kaptcha.image.width","300");
        // 图片高度
        properties.setProperty("kaptcha.image.height","100");
        // 文字个数
        properties.setProperty("kaptcha.textproducer.char.length","4");
        //文字大小
        properties.setProperty("kaptcha.textproducer.font.size","100");
        //文字随机字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体");
        //文字距离
        properties.setProperty("kaptcha.textproducer.char.space","16");
        //干扰线颜色
        properties.setProperty("kaptcha.noise.color","blue");
        // 文本内容 从设置字符中随机抽取
        properties.setProperty("kaptcha.textproducer.char.string","0123456789");
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        kaptcha.setConfig(new Config(properties));
        return kaptcha;
    }
}

验证码接口

/**
 * 生成验证码
 */
@GetMapping("/verify-code")
public void getVerifyCode(HttpServletResponse resp, HttpSession session) throws IOException {
    resp.setContentType("image/jpeg");
    // 生成图形校验码内容
    String text = producer.createText();
    // 将验证码内容存入HttpSession
    session.setAttribute("verify_code", text);
    // 生成图形校验码图片
    BufferedImage image = producer.createImage(text);
    // 使用try-with-resources 方式,可以自动关闭流
    try(ServletOutputStream out = resp.getOutputStream()) {
        // 将校验码图片信息输出到浏览器
        ImageIO.write(image, "jpeg", out);
    }
}

代码注释写的很清楚,就不过多的介绍。属于固定的配置,既然配置完了,那就看看生成的效果吧!

接下来就看看如何集成到 Spring Security 中的认证逻辑吧!

加入依赖

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

基于过滤器

编写自定义认证逻辑

这里继承的过滤器为 UsernamePasswordAuthenticationFilter,并重写attemptAuthentication方法。用户登录的用户名/密码是在 UsernamePasswordAuthenticationFilter 类中处理,那我们就继承这个类,增加对验证码的处理。当然也可以实现其他类型的过滤器,比如:GenericFilterBeanOncePerRequestFilter,不过处理起来会比继承UsernamePasswordAuthenticationFilter麻烦一点。

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class VerifyCodeFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 需要是 POST 请求
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        // 获得请求验证码值
        String code = request.getParameter("code");
        HttpSession session = request.getSession();
        // 获得 session 中的 验证码值
        String sessionVerifyCode = (String) session.getAttribute("verify_code");
        if (StringUtils.isEmpty(code)){
            throw new AuthenticationServiceException("验证码不能为空!");
        }
        if(StringUtils.isEmpty(sessionVerifyCode)){
            throw new AuthenticationServiceException("请重新申请验证码!");
        }
        if (!sessionVerifyCode.equalsIgnoreCase(code)) {
            throw new AuthenticationServiceException("验证码错误!");
        }
        // 验证码验证成功,清除 session 中的验证码
        session.removeAttribute("verify_code");
        // 验证码验证成功,走原本父类认证逻辑
        return super.attemptAuthentication(request, response);
    }
}

代码逻辑很简单,验证验证码是否正确,正确则走父类原本逻辑,去验证用户名密码是否正确。 过滤器定义完成后,接下来就是用我们自定义的过滤器代替默认的 UsernamePasswordAuthenticationFilter

  • SecurityConfig
import cn.cxyxj.study04.Authentication.config.MyAuthenticationFailureHandler;
import cn.cxyxj.study04.Authentication.config.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("cxyxj").password("123").roles("admin").build());
        manager.createUser(User.withUsername("security").password("security").roles("user").build());
        return manager;
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 用自定义的 VerifyCodeFilter 实例代替 UsernamePasswordAuthenticationFilter
        http.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);
        http.authorizeRequests()  //开启配置
                // 验证码、登录接口放行
                .antMatchers("/verify-code","/auth/login").permitAll()
                .anyRequest() //其他请求
                .authenticated().and()//验证   表示其他请求需要登录才能访问
                .csrf().disable();  // 禁用 csrf 保护
    }
    @Bean
    VerifyCodeFilter loginFilter() throws Exception {
        VerifyCodeFilter verifyCodeFilter = new VerifyCodeFilter();
        verifyCodeFilter.setFilterProcessesUrl("/auth/login");
        verifyCodeFilter.setUsernameParameter("account");
        verifyCodeFilter.setPasswordParameter("pwd");
        verifyCodeFilter.setAuthenticationManager(authenticationManagerBean());
        verifyCodeFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
        verifyCodeFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
        return verifyCodeFilter;
    }
}

当我们替换了 UsernamePasswordAuthenticationFilter 之后,原本在 SecurityConfig#configure 方法中关于 form 表单的配置就会失效,那些失效的属性,都可以在配置 VerifyCodeFilter 实例的时候配置;还需要记得配置AuthenticationManager,否则启动时会报错。

  • MyAuthenticationFailureHandler
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 登录失败回调
 */
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        String msg = "";
        if (e instanceof LockedException) {
            msg = "账户被锁定,请联系管理员!";
        }
       else if (e instanceof BadCredentialsException) {
            msg = "用户名或者密码输入错误,请重新输入!";
        }
        out.write(e.getMessage());
        out.flush();
        out.close();
    }
}
  • MyAuthenticationSuccessHandler
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 登录成功回调
 */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Object principal = authentication.getPrincipal();
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(principal));
        out.flush();
        out.close();
    }
}

测试

  • 不传入验证码发起请求。

  • 请求获取验证码接口

  • 输入错误的验证码

  • 输入正确的验证码

输入已经使用过的验证码

各位读者是不是会觉得既然继承了 Filter,那是不是每个接口都会进入到我们的自定义方法中呀!如果是继承了 GenericFilterBean、OncePerRequestFilter 那是肯定会的,需要手动处理。 但我们继承的是 UsernamePasswordAuthenticationFilter,security 已经帮忙处理了。处理逻辑在其父类 AbstractAuthenticationProcessingFilter#doFilter 中。

基于认证器

编写自定义认证逻辑

验证码逻辑编写完成,那接下来就自定义一个 VerifyCodeAuthenticationProvider 继承自 DaoAuthenticationProvider,并重写 authenticate 方法。

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
 * 验证码验证器
 */
public class VerifyCodeAuthenticationProvider extends DaoAuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 获得请求验证码值
        String code = req.getParameter("code");
        // 获得 session 中的 验证码值
        HttpSession session = req.getSession();
        String sessionVerifyCode = (String) session.getAttribute("verify_code");
        if (StringUtils.isEmpty(code)){
            throw new AuthenticationServiceException("验证码不能为空!");
        }
        if(StringUtils.isEmpty(sessionVerifyCode)){
            throw new AuthenticationServiceException("请重新申请验证码!");
        }
        if (!code.toLowerCase().equals(sessionVerifyCode.toLowerCase())) {
            throw new AuthenticationServiceException("验证码错误!");
        }
        // 验证码验证成功,清除 session 中的验证码
        session.removeAttribute("verify_code");
        // 验证码验证成功,走原本父类认证逻辑
        return super.authenticate(authentication);
    }
}

自定义的认证逻辑完成了,剩下的问题就是如何让 security 走我们的认证逻辑了。

在 security 中,所有的 AuthenticationProvider 都是放在 ProviderManager 中统一管理的,所以接下来我们就要自己提供 ProviderManager,然后注入自定义的 VerifyCodeAuthenticationProvider。

  • SecurityConfig
import cn.cxyxj.study02.config.MyAuthenticationFailureHandler;
import cn.cxyxj.study02.config.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("cxyxj").password("123").roles("admin").build());
        manager.createUser(User.withUsername("security").password("security").roles("user").build());
        return manager;
    }
    @Bean
    VerifyCodeAuthenticationProvider verifyCodeAuthenticationProvider() {
        VerifyCodeAuthenticationProvider provider = new VerifyCodeAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsService(userDetailsService());
        return provider;
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        ProviderManager manager = new ProviderManager(verifyCodeAuthenticationProvider());
        return manager;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()  //开启配置
                // 验证码接口放行
                .antMatchers("/verify-code").permitAll()
                .anyRequest() //其他请求
                .authenticated()//验证   表示其他请求需要登录才能访问
                .and()
                .formLogin()
                .loginPage("/login.html") //登录页面
                .loginProcessingUrl("/auth/login") //登录接口,此地址可以不真实存在
                .usernameParameter("account") //用户名字段
                .passwordParameter("pwd") //密码字段
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureHandler(new MyAuthenticationFailureHandler())
                .permitAll() // 上述 login.html 页面、/auth/login接口放行
                .and()
                .csrf().disable();  // 禁用 csrf 保护
        ;
    }
}

测试

不传入验证码发起请求。

  • 请求获取验证码接口

  • 输入错误的验证码

  • 输入正确的验证码

  • 输入已经使用过的验证码

以上就是Spring Security 登录时添加图形验证码实现实例的详细内容,更多关于Spring Security 登录图形验证码的资料请关注我们其它相关文章!

(0)

相关推荐

  • Springboot+SpringSecurity实现图片验证码登录的示例

    这个问题,网上找了好多,结果代码都不全,找了好多,要不是就自动注入的类注入不了,编译报错,要不异常捕获不了浪费好多时间,就觉得,框架不熟就不能随便用,全是坑,气死我了,最后改了两天.终于弄好啦; 问题主要是: 返回的验证码不知道在SpringSecurity的什么地方和存在内存里的比较?我用的方法是前置一个过滤器,插入到表单验证之前. 比较之后应该怎么处理,:比较之后要抛出一个继承了AuthenticationException的异常 其次是捕获验证码错误异常的处理? 捕获到的异常交给自定义验证

  • Spring Security 图片验证码功能的实例代码

    验证码逻辑 以前在项目中也做过验证码,生成验证码的代码网上有很多,也有一些第三方的jar包也可以生成漂亮的验证码.验证码逻辑很简单,就是在登录页放一个image标签,src指向一个controller,这个Controller返回把生成的图片以输出流返回给页面,生成图片的同时把图片上的文本放在session,登录的时候带过来输入的验证码,从session中取出,两者对比.这位老师讲的用Spring Security集成验证码,大体思路和我说的一样,但更加规范和通用些. spring securi

  • SpringSecurity页面授权与登录验证实现(内存取值与数据库取值)

    目录 SpringSecurity? 一.导入依赖 二.配置yml文件 三.代码部分 DAO层(注意@Repository与@Mapper注解) Service层(注意@Service注解) Controller层(注意@Controller注解) POJO Utils 资源目录结构 运行效果 SpringSecurity? Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean

  • Spring Security实现添加图片验证功能

    目录 本章内容 思路 方案 怎么将字符串变成图片验证码? kaptcha这么玩 hutool这么玩 传统web项目 过滤器方式 认证器方式 总结下 前后端分离项目 基于过滤器方式 基于认证器方式 本章内容 Spring security添加图片验证方式,在互联网上面有很多这种博客,都写的非常的详细了.本篇主要讲一些添加图片验证的思路.还有前后端分离方式,图片验证要怎么去处理? 图片验证的思路 简单的demo 思路 小白: "我们从总体流程上看图片验证在认证的哪一个阶段?" 小黑: &q

  • Spring Security组件一键接入验证码登录和小程序登录的详细过程

    目录 DSL配置风格 为什么这么灵活? 使用方法 普通登录 验证码登录 小程序登录 最近实现了一个多端登录的Spring Security组件,用起来非常丝滑,开箱即用,可插拔,而且灵活性非常强.我觉得能满足大部分场景的需要.目前完成了手机号验证码和微信小程序两种自定义登录,加上默认的Form登录,一共三种,现在开源分享给大家,接下来简单介绍一下这个插件包. DSL配置风格 切入正题,先来看看配置: @Bean SecurityFilterChain defaultSecurityFilterC

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

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

  • 浅析Spring Security登录验证流程源码

    一.登录认证基于过滤器链 Spring Security的登录验证流程核心就是过滤器链.当一个请求到达时按照过滤器链的顺序依次进行处理,通过所有过滤器链的验证,就可以访问API接口了. SpringSecurity提供了多种登录认证的方式,由多种Filter过滤器来实现,比如: BasicAuthenticationFilter实现的是HttpBasic模式的登录认证 UsernamePasswordAuthenticationFilter实现用户名密码的登录认证 RememberMeAuthe

  • Spring Security登录表单配置示例详解

    目录 Spring Security登录表单配置 1.引入pom依赖 2.bootstrap.yml添加配置 3.创建login.html 4.创建配置类 5.配置细节 6.登陆成功 7.登陆失败 8.注销登录 Spring Security登录表单配置 1.引入pom依赖 ​ 创建一个Spring Boot工程,引入Web和Spring Security依赖: <?xml version="1.0" encoding="UTF-8"?> <pro

  • spring security登录成功后跳转回登录前的页面

    目录 spring security登录成功后跳转回登录前的页面 需求如下 代码如下 Springsecurity 配置文件和登录跳转 项目结构 直接上springsecurity配置文件 自定义的登录页面login.html上需要加form标签登录框 具体修改如下 spring security登录成功后跳转回登录前的页面 我刚好碰到了这么一个需求,正好自己也刚开始学spring security,但是我百度了一下,发现都讲的好麻烦,其实大概了解完之后,亲自实践一下发现,操作非常简单. 需求如

  • vue实现登录时的图片验证码

    本文实例为大家分享了vue实现登录时的图片验证码的具体代码,供大家参考,具体内容如下 效果图 一.新建vue组件components/identify/identify.vue <template> <div class="s-canvas"> <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></c

  • SpringSecurity添加图形验证码认证实现

    目录 第一步:图形验证码接口 1.使用第三方的验证码生成工具Kaptcha 2.设置验证接口 3.模板表单设置 第二步:设置图像验证过滤器 第三步:将图像验证过滤器添加到springsecurity过滤器链中 第一步:图形验证码接口 1.使用第三方的验证码生成工具Kaptcha https://github.com/penggle/kaptcha @Configuration public class KaptchaImageCodeConfig { @Bean public DefaultKa

  • node+vue前后端分离实现登录时使用图片验证码功能

    目录 后端代码 前端代码 获取验证码方法 登录验证方法 记录一下前端使用验证码登录的过程后端用的是node.js,关键模块是svg-captcha前端使用的是vue2最后的登录界面如下: 后端代码 先上代码,然后解释 const svgCaptcha = require('svg-captcha') exports.checkCode = (req, res) => { const img = svgCaptcha.create({ size: 4, ignoreChars: '0o1l', c

  • Python3爬虫中识别图形验证码的实例讲解

    本节我们首先来尝试识别最简单的一种验证码,图形验证码,这种验证码出现的最早,现在也很常见,一般是四位字母或者数字组成的,例如中国知网的注册页面就有类似的验证码,链接为:http://my.cnki.net/elibregister/commonRegister.aspx,页面: 表单的最后一项就是图形验证码,我们必须完全输入正确图中的字符才可以完成注册. 1.本节目标 本节我们就以知网的验证码为例,讲解一下利用 OCR 技术识别此种图形验证码的方法. 2. 准备工作 识别图形验证码需要的库有 T

  • Spring Security登录添加验证码的实现过程

    登录添加验证码是一个非常常见的需求,网上也有非常成熟的解决方案,其实,要是自己自定义登录实现这个并不难,但是如果需要在 Spring Security 框架中实现这个功能,还得稍费一点功夫,本文就和小伙伴来分享下在 Spring Security 框架中如何添加验证码. 关于 Spring Security 基本配置,这里就不再多说,小伙伴有不懂的可以参考我的书<SpringBoot+Vue全栈开发实战>,本文主要来看如何加入验证码功能. 准备验证码 要有验证码,首先得先准备好验证码,本文采用

  • Spring Security OAuth2集成短信验证码登录以及第三方登录

    前言 基于SpringCloud做微服务架构分布式系统时,OAuth2.0作为认证的业内标准,Spring Security OAuth2也提供了全套的解决方案来支持在Spring Cloud/Spring Boot环境下使用OAuth2.0,提供了开箱即用的组件.但是在开发过程中我们会发现由于Spring Security OAuth2的组件特别全面,这样就导致了扩展很不方便或者说是不太容易直指定扩展的方案,例如: 图片验证码登录 短信验证码登录 微信小程序登录 第三方系统登录 CAS单点登录

随机推荐