在之前的博文《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. 使用MindSpore_hub 进行 加载模型用于推理或迁移学习

    从官方资料: https://www.mindspore.cn/tutorial/training/zh-CN/r1.2/use/save_model.html?highlight=save_chec ...

  2. Chrome(Google) 浏览器安装Vue2、Vue3插件方法 (亲测有效)

    1.背景 2.步骤 步骤一:下载插件 网站:https://chrome.zzzmh.cn/#/search 百度网盘下载: 链接:https://pan.baidu.com/s/1xdEK-7qVx ...

  3. 后端开发学习敏捷需求-->产品价值的定位

    产品价值的定位 为什么要写这一系列文章 2023年网上报名学习了,敏捷软件需求的培训课程 ,一直都没有进行回顾,回顾学习,总结 业务分析的能力偏弱,学习和了解关于业务需求相关的方法和理论 每一年都有一 ...

  4. 如何利用HMMER鉴定基因家族成员

    通常我们用的都是通过blast比对来确定我们需要的家族成员,首先是比对序列,再次是需要目标物种的蛋白序列,来进行比对,通常比对的时候我们都需要设定e-value值.今天我们来学习一下利用HMMER来鉴 ...

  5. [rCore学习笔记 024]多道程序与协作式调度

    写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 本节重 ...

  6. Keil uVision5软件破解方法

    1.正常下载软件 2.右键"管理员方式运行"软件 3.如下图"File"->"License..." 4.复制CID,管理员方式打开破 ...

  7. Java IO流的简单使用 通俗易懂 超详细 【内含案例】

    IO流简单使用 InputStream 字节输入流 OutputStream 字节输出流 Reader 字符输入流 Writer 字符输出流 代码示例 输入和输出是相对于程序来说的,读取到程序中叫做输 ...

  8. jxls导入excel

    我们在开发中经常用jxls实现导出功能,殊不知jxls也有导入功能,下面来介绍下如何使用jxls导入excel. 首先在maven的pom中添加相关依赖,如下: <dependency> ...

  9. Linux驱动小技巧 | 利用DRIVER_ATTR实现调用内核函数

    1. 前言 很多朋友在调试驱动的时候,都会遇到这样一个场景: 修改一个参数,然后调用某个内核中的函数. 比如将某个gpio的值拉高/拉低,修改某个寄存器的值等等. 如果每一个参数都通过字符设备的ioc ...

  10. hacs安装

    安装 HACS 直接使用 Docker 的可视化管理面板 Portainer 或者通过命令行进入 Docker 容器,然后执行以下安装命令: docker exec -it <容器名称或容器ID ...