在之前的博文《SpringBoot进阶教程(八十)Spring Security》中,已经介绍了在Spring Security中如何基于formLogin认证、基于HttpBasic认证和自定义用户名和密码。这篇文章,我们将介绍自定义登录界面的登录验证方式。在上一篇博文《SpringBoot进阶教程(八十一)Spring Security自定义认证》中,已经介绍了如何实现Spring Security自定义认证。

v生成图形验证码

添加maven依赖
        <dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
创建验证码对象
/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@Data
public class ImageCode {
/**
* image图片
*/
private BufferedImage image;
/**
* 验证码
*/
private String code;
/**
* 过期时间
*/
private LocalDateTime expireTime; public ImageCode(BufferedImage image, String code, int expireIn) {
this.image = image;
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
} /**
* 判断验证码是否已过期
* @return
*/
public boolean isExpire() {
return LocalDateTime.now().isAfter(expireTime);
}
}
创建ImageController

编写接口,返回图形验证码:

/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@RestController
public class ImageController {
public final static String SESSION_KEY_IMAGE_CODE = "SESSION_VERIFICATION_CODE"; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageCode imageCode = createImageCode();
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_IMAGE_CODE, imageCode);
ImageIO.write(imageCode.getImage(), "jpeg", response.getOutputStream());
} private ImageCode createImageCode() {
// 验证码图片宽度
int width = 100;
// 验证码图片长度
int height = 36;
// 验证码位数
int length = 4;
// 验证码有效时间 60s
int expireIn = 60; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics graphics = image.getGraphics(); Random random = new Random(); graphics.setColor(getRandColor(200, 500));
graphics.fillRect(0, 0, width, height);
graphics.setFont(new Font("Times New Roman", Font.ITALIC, 20));
graphics.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
graphics.drawLine(x, y, x + xl, y + yl);
}
StringBuilder sRand = new StringBuilder();
for (int i = 0; i < length; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand.append(rand);
graphics.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
graphics.drawString(rand, 13 * i + 6, 16);
} graphics.dispose(); return new ImageCode(image, sRand.toString(), expireIn);
} 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);
}
}

org.springframework.social.connect.web.HttpSessionSessionStrategy对象封装了一些处理Session的方法,包含了setAttribute、getAttribute和removeAttribute方法,具体可以查看该类的源码。使用sessionStrategy将生成的验证码对象存储到Session中,并通过IO流将生成的图片输出到登录页面上。

v改造登录页面

添加验证码控件

在上一篇博文《SpringBoot进阶教程(八十一)Spring Security自定义认证》中的"重写form登录页",已经创建了login.html,在login.html中添加如下代码:

        <span style="display: inline">
<input type="text" name="请输入验证码" placeholder="验证码" required="required"/>
<img src="/code/image"/>
</span>

img标签的src属性对应ImageController的createImageCode方法。

v认证流程添加验证码效验

定义异常类

在校验验证码的过程中,可能会抛出各种验证码类型的异常,比如“验证码错误”、“验证码已过期”等,所以我们定义一个验证码类型的异常类:

/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
public class ValidateCodeException extends AuthenticationException { private static final long serialVersionUID = 1715361291615299823L; public ValidateCodeException(String explanation) {
super(explanation);
}
}

注意:这里继承的是AuthenticationException而不是Exception。

创建验证码的校验过滤器

Spring Security实际上是由许多过滤器组成的过滤器链,处理用户登录逻辑的过滤器为UsernamePasswordAuthenticationFilter,而验证码校验过程应该是在这个过滤器之前的,即只有验证码校验通过后才去校验用户名和密码。由于Spring Security并没有直接提供验证码校验相关的过滤器接口,所以我们需要自己定义一个验证码校验的过滤器ValidateCodeFilter:

/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@Component
public class ValidateCodeFilter extends OncePerRequestFilter { @Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if ("/login".equalsIgnoreCase(httpServletRequest.getRequestURI())
&& "post".equalsIgnoreCase(httpServletRequest.getMethod())) {
try {
validateCode(new ServletWebRequest(httpServletRequest));
} catch (ValidateCodeException e) {
myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
} private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException, ValidateCodeException {
ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(servletWebRequest, ImageController.SESSION_KEY_IMAGE_CODE);
String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode"); if (StringUtils.isEmpty(codeInRequest)) {
throw new ValidateCodeException("验证码不能为空!");
}
if (codeInSession == null) {
throw new ValidateCodeException("验证码不存在!");
}
if (codeInSession.isExpire()) {
sessionStrategy.removeAttribute(servletWebRequest, ImageController.SESSION_KEY_IMAGE_CODE);
throw new ValidateCodeException("验证码已过期!");
}
if (!codeInRequest.equalsIgnoreCase(codeInSession.getCode())) {
throw new ValidateCodeException("验证码不正确!");
} sessionStrategy.removeAttribute(servletWebRequest, ImageController.SESSION_KEY_IMAGE_CODE);
}
}

ValidateCodeFilter继承了org.springframework.web.filter.OncePerRequestFilter,该过滤器只会执行一次。ValidateCodeFilter继承了org.springframework.web.filter.OncePerRequestFilter,该过滤器只会执行一次。

doFilterInternal方法中我们判断了请求URL是否为/login,该路径对应登录form表单的action路径,请求的方法是否为POST,是的话进行验证码校验逻辑,否则直接执行filterChain.doFilter让代码往下走。当在验证码校验的过程中捕获到异常时,调用Spring Security的校验失败处理器AuthenticationFailureHandler进行处理。

我们分别从Session中获取了ImageCode对象和请求参数imageCode(对应登录页面的验证码input框name属性),然后进行了各种判断并抛出相应的异常。当验证码过期或者验证码校验通过时,我们便可以删除Session中的ImageCode属性了。

v更新配置类

验证码校验过滤器定义好了,怎么才能将其添加到UsernamePasswordAuthenticationFilter前面呢?很简单,只需要在BrowserSecurityConfig的configure方法中添加些许配置即可,顺便配置验证码请求不配拦截: "/code/image"。

/**
* @Author chen bo
* @Date 2023/12
* @Des
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new ValidateCodeFilter(), UsernamePasswordAuthenticationFilter.class) //添加验证码效验过滤器
.formLogin() // 表单登录
.loginPage("/login.html") // 登录跳转url
// .loginPage("/authentication/require")
.loginProcessingUrl("/login") // 处理表单登录url
// .successHandler(authenticationSuccessHandler)
.failureHandler(new MyAuthenticationFailureHandler())
.and()
.authorizeRequests() // 授权配置
.antMatchers("/login.html", "/css/**", "/authentication/require", "/code/image").permitAll() // 无需认证
.anyRequest() // 所有请求
.authenticated() // 都需要认证
.and().csrf().disable(); } @Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

上面代码中,我们注入了ValidateCodeFilter,然后通过addFilterBefore方法将ValidateCodeFilter验证码校验过滤器添加到了UsernamePasswordAuthenticationFilter前面。

v运行效果图

其他参考/学习资料:

v源码地址

https://github.com/toutouge/javademosecond/tree/master/security-demo

作  者:请叫我头头哥


出  处:http://www.cnblogs.com/toutou/


关于作者:专注于基础平台的项目开发。如有问题或建议,请多多赐教!


版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。


特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信


声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是作者坚持原创和持续写作的最大动力!

#comment_body_3242240 { display: none }

SpringBoot进阶教程(八十二)Spring Security图形验证码的更多相关文章

  1. SpringBoot进阶教程(六十二)整合Kafka

    在上一篇文章<Linux安装Kafka>中,已经介绍了如何在Linux安装Kafka,以及Kafka的启动/关闭和创建发话题并产生消息和消费消息.这篇文章就介绍介绍SpringBoot整合 ...

  2. SpringBoot进阶教程(五十九)整合Codis

    上一篇博文<详解Codis安装与部署>中,详细介绍了codis的安装与部署,这篇文章主要介绍介绍springboot整合codis.如果之前看过<SpringBoot进阶教程(五十二 ...

  3. SpringBoot进阶教程(六十四)注解大全

    在Spring1.x时代,还没出现注解,需要大量xml配置文件并在内部编写大量bean标签.Java5推出新特性annotation,为spring的更新奠定了基础.从Spring 2.X开始spri ...

  4. SpringBoot进阶教程(六十五)自定义注解

    在上一篇文章<SpringBoot进阶教程(六十四)注解大全>中介绍了springboot的常用注解,springboot提供的注解非常的多,这些注解简化了我们的很多操作.今天主要介绍介绍 ...

  5. SpringBoot进阶教程(六十八)Sentinel实现限流降级

    前面两篇文章nginx限流配置和SpringBoot进阶教程(六十七)RateLimiter限流,我们介绍了如何使用nginx和RateLimiter限流,这篇文章介绍另外一种限流方式---Senti ...

  6. SpringBoot进阶教程(七十四)整合ELK

    在上一篇文章<SpringBoot进阶教程(七十三)整合elasticsearch >,已经详细介绍了关于elasticsearch的安装与使用,现在主要来看看关于ELK的定义.安装及使用 ...

  7. SpringBoot进阶教程(六十)intellij idea project下建多个module搭建架构(上)

    在 IntelliJ IDEA 中,没有类似于 Eclipse 工作空间(Workspace)的概念,而是提出了Project和Module这两个概念.多module有一个父maven工程,多个子工程 ...

  8. SpringBoot非官方教程 | 第二十二篇: 创建含有多module的springboot工程

    转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/11/springbot22-modules/ 本文出自方志朋的博客 这篇文 ...

  9. SpringBoot进阶教程(七十)SkyWalking

    流行的APM(Application Performance Management工具有很多,比如Cat.Zipkin.Pinpoint.SkyWalking.优秀的监控工具还有很多,其它比如还有za ...

  10. Spring Boot2 系列教程(三十二)Spring Boot 整合 Shiro

    在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro. 今天松哥就来和大家聊聊 Spring Boot ...

随机推荐

  1. 【Java】IDEA普通JavaEE项目实现SSM整合

    一.需要的组件 首先是解决组件问题,非Maven项目构建的jar包 [Servlet & JSP & JSTL] 虽然不是Maven项目,但是JSTL的组件是需要引入的 这里就按照Ma ...

  2. python网络连接报错:ValueError("Unable to determine SOCKS version from %s" % proxy_url) ValueError: Unable to determine SOCKS version from socks://192.168.1.100:1080/

    python应用proxy网络连接报错: return super().send(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...

  3. Mybatis-Plus系统化学习之环境准备与简单使用

    1.背景 平时在开发中会经常用到单表的CRUD操作 其实,这些单表的CRUD,完全不需要我们写sql,可以使用mybatis-plus自动生成,不但高效而且不容用出错! 2.mybatis-plus的 ...

  4. 禅道项目管理系统权限绕过漏洞(QVD-2024-15263)

    本文所涉及的任何技术.信息或工具,仅供学习和参考之用,请勿将文章内的相关技术用于非法目的,如有相关非法行为与文章作者无关.请遵守<中华人民共和国网络安全法>. 1. 概述 1.1 基本信息 ...

  5. PHP中的Malformed UTF-8 characters错误解决

    在PHP开发中,开发者经常会遇到Malformed UTF-8 characters错误.这个错误通常是由于代码中存在无效的UTF-8字符而引起的.本篇博客将为您介绍如何解决这个问题. 什么是UTF- ...

  6. Windows下cmd中cd命令不起作用的原因和解决办法

    Windows下cmd中cd命令不起作用的原因和解决办法 如图:cd命令无效 原因:windows系统cmd换目录跨磁盘的话需要先进行磁盘的转换

  7. win7安装snmp服务

    一.安装SNMP Win7操作系统默认情况下是不安装SNMP服务的,今天讲解一下在Win7操作系统下安装SNMP,具体安装步骤如下: 打开控制面板--卸载程序 WIN7操作系统下安装SNMP的步骤如下 ...

  8. Java元注解介绍

    Java四种元注解相关介绍 概述 注解从Java1.5引入以来,不断地简化我们编写代码的流程,逐渐的也成为了我们必学的一项技术.我们学习了各种注解,学习了他们的用法,学习了他们的限制,是否想过他们的组 ...

  9. 二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程)

    二. Spring Boot 中的 "依赖管理和自动配置" 详解透彻到底(附+详细代码流程) @ 目录 二. Spring Boot 中的 "依赖管理和自动配置" ...

  10. Kubernetes-6:Pod生命周期介绍(init Container)

    Pod生命周期 生命周期 1.API server调用kubelet下达Pod创建指令 2.容器环境初始化 3.进入Pod生命周期内(Pod开始创建) 4.Pod只要创建,就会自动生成一个pause容 ...