springsecurity-6-基于filter实现图形验证码

springboot葵花宝典      2022-03-26     352

关键词:

SpringSecurity-6-基于Filter实现图形验证码

SpringSecurity中有多种方式实现图像验证码,使用自定义过滤器去处理验证码逻辑是最简单的方式,只要将过滤器添加到合适的位置,当登录的时候,对验证码进行校验,成功就放行,失败则抛出异常。

图形验证流程

流程图如下

SpringSecurity-6-基于Filter实现图形验证码_登录页面

使用kaptcha生成图形验证码

kaptcha是谷歌提供的一款开源验证码jar包,只需简单配置就可以生成图片,源码地址:https://github.com/penggle/kaptcha

添加kaptcha依赖

在项目的​pom.xml​中添加相关依赖

<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>

验证码生成配置类

在​​config​​​​包​​​下创建一个​​KaptchaCodeConfig​​类作为验证码生成配置类,代码如下

@Component
public class KaptchaCodeConfig
@Bean
public DefaultKaptcha getDefaultKaptcha()
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "192,192,192");
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "110");
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "36");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "28");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋体");
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 图片效果
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config); return defaultKaptcha;

抽取属性

当然了这是一个死配置,我们一般会使用​​xxx.properties​​​+@PropertySource将配置属性抽取出来,如果不了解这个注解的,可以查看我之前对​​SpringBoot常用注解汇总​​里面对这些注解都有介绍,具体实现如下:

  • kaptcha.properties的 实现
kaptcha.BORDER=yes
kaptcha.BORDER_COLOR=192,192,192
kaptcha.IMAGE_WIDTH=110
kaptcha.IMAGE_HEIGHT=36
kaptcha.FONT_COLOR=blue
kaptcha.FONT_SIZE=28
kaptcha.FONT_NAMES=宋体,楷体,微软雅黑
kaptcha.CHAR_LENGTH=4
kaptcha.OBSCURIFICATOR_IMPL=com.google.code.kaptcha.impl.ShadowGimpy
  • 重新实现KaptchaCodeConfig类
@Component
@PropertySource("classpath:kaptcha.properties")
public class KaptchaCodeConfig
@Value("$kaptcha.BORDER")
private String BORDER ;
@Value("$kaptcha.BORDER_COLOR")
private String BORDER_COLOR ;
@Value("$kaptcha.IMAGE_WIDTH")
private String IMAGE_WIDTH ;
@Value("$kaptcha.IMAGE_HEIGHT")
private String IMAGE_HEIGHT ;
@Value("$kaptcha.FONT_COLOR")
private String FONT_COLOR ;
@Value("$kaptcha.FONT_SIZE")
private String FONT_SIZE ;
@Value("$kaptcha.FONT_NAMES")
private String FONT_NAMES ;
@Value("$kaptcha.CHAR_LENGTH")
private String CHAR_LENGTH ;
@Value("$kaptcha.OBSCURIFICATOR_IMPL")
private String OBSCURIFICATOR_IMPL ;


@Bean
public DefaultKaptcha getDefaultKaptcha()
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty(Constants.KAPTCHA_BORDER, BORDER);
properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, BORDER_COLOR);
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, IMAGE_WIDTH);
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, IMAGE_HEIGHT);
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, FONT_COLOR);
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, FONT_SIZE);
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, FONT_NAMES);
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH,CHAR_LENGTH); // 图片效果
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, OBSCURIFICATOR_IMPL);
Config config = new Config(properties);
defaultKaptcha.setConfig(config); return defaultKaptcha;

生成图片的接口实现

使用CaptchaController实现验证码接口

@Slf4j
@RestController
public class CaptchaController
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";

@Autowired
private DefaultKaptcha defaultKaptcha; /**
* 获取图形验证码
*/
@RequestMapping("/code/image")
public void imageCode(HttpServletRequest request, HttpServletResponse response) throws IOException

response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
// 1. 获取验证码字符串
String code = defaultKaptcha.createText();
log.info("生成的图形验证码是:" + code);
// 2. 字符串把它放到session中
request.getSession().setAttribute(SESSION_KEY , code);
// 3. 获取验证码图片
BufferedImage image = defaultKaptcha.createImage(code);
// 4. 将验证码图片把它写出去
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpg", out);

/code/image接口免验证

因为这是登录页面使用的,所以要免登录,因此需要在​​LearnSrpingSecurity.configure(HttpSecurity http)​

中放行 /code/image 资源权限

SpringSecurity-6-基于Filter实现图形验证码_spring_02

重构login.html

把如下代码加入到登录页面合适的位置,注意图片img标签放到登录表单中。

<!--suppress ALL-->
<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>springboot葵花宝典登录页面</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">

</head>
<body>
<h1>springboot葵花宝典登录页面</h1>
<form th:action="@/login/form" action="index.html" method="post">
<span>用户名称</span><input type="text" name="username" /> <br>
<span>用户密码</span><input type="password" name="password" /> <br>
<div >
<input name="code" type="text" class="form-control" placeholder="验证码">
<img onclick="this.src=/code/image?+Math.random()" src="/code/image" />
</div>


<div th:if="$param.error">
<span th:text="$session.SPRING_SECURITY_LAST_EXCEPTION.message" style="color:#ff0000">用户名或 密码错误</span>
</div>
<input type="submit" value="登陆">

</form>

</body>
</html>

实现效果

重新启动项目,得到的登录页面效果如下

SpringSecurity-6-基于Filter实现图形验证码_spring_03

实现验证码校验过滤器

  • 编写自己的过滤器​​ImageCodeValidateFilter​​​,让其继承​​OncePerRequestFilter​​(可以保证每一次请求只使用一次该过滤器)
  • 添加@Component注解
  • 在​​ImageCodeValidateFilter​​过滤器中从seesion获取验证码文字与用户输入比对,比对通过执行其他过滤器链
  • 比对不通过,抛出SessionAuthenticationException异常,交给AuthenticationFailureHandler处理,提示信息通过自定义异常 ValidateCodeExcetipn 抛出
  • 最后将​​ImageCodeValidateFilter​​放在UsernamePasswordAuthenticationFilter表单过滤器之前执行
@Component
public class ImageCodeValidateFilter extends OncePerRequestFilter


@Autowired
private MyAuthenticationFailureHandler failureHandler;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException

// 必须是登录的post请求才能进行验证,其他的直接放行
if(StringUtils.equals("/login/form",request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(),"post"))
try
//1.验证谜底与用户输入是否匹配
validate(request);
catch(AuthenticationException e)
//2.捕获步骤1中校验出现异常,交给失败处理类进行进行处理
failureHandler.onAuthenticationFailure(request,response,e);
return;


//通过校验,就放行
filterChain.doFilter(request,response);


private void validate(HttpServletRequest request) throws ServletRequestBindingException

String sessionCode = (String)request.getSession().getAttribute(CaptchaController.SESSION_KEY);
String inpuCode = request.getParameter("code");
if(StringUtils.isEmpty(inpuCode))
throw new SessionAuthenticationException("验证码不能为空");

if(!inpuCode.equalsIgnoreCase(sessionCode))
throw new ValidateCodeException("验证码不能为空");


创建验证码异常类

在exception包下创建ValidateCodeException,继承AuthenticationException

​:​org.springframework.security.core.AuthenticationException

import org.springframework.security.core.AuthenticationException;

public class ValidateCodeException extends AuthenticationException
public ValidateCodeException(String msg, Throwable cause)
super(msg, cause);


public ValidateCodeException(String msg)
super(msg);

  • 修改MyAuthenticationFailureHandler类
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler
private static ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException
// 当认证失败后,响应 JSON 数据给前端
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));

重构​ ​LearnSrpingSecurity

  1. 将ImageCodeValidateFilter过滤器添加到UsernamePasswordAuthenticationFilter过滤器前面,需要在configure(HttpSecurity http)方法中进行修改

具体步骤:

  • 注入ImageCodeValidateFilter
@Autowired
private ImageCodeValidateFilter codeValidateFilter;
  • 把 ImageCodeValidateFilter 添加 UsernamePasswordAuthenticationFilter 实例前

SpringSecurity-6-基于Filter实现图形验证码_spring_04

http.csrf().disable()  //禁用跨站csrf防御,后面的章节会专门讲解
.addFilterBefore(codeValidateFilter, UsernamePasswordAuthenticationFilter.class)

测试

  • 不输入验证码

SpringSecurity-6-基于Filter实现图形验证码_spring_05

  • 输入错误验证码

SpringSecurity-6-基于Filter实现图形验证码_登录页面_06

  • 输入正确验证码

SpringSecurity-6-基于Filter实现图形验证码_验证码_07


如果您觉得本文不错,​欢迎关注,点赞,收藏支持​,您的关注是我坚持的动力!

原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!


基于filter实现条件路由(代码片段)

基于filter实现条件路由Intro在我们的项目有几个测试用的接口,有的接口我们往往不想在生产环境上使用,于是会在代码里判断当前环境是不是生产环境,如果不是生产环境才允许执行,否则就返回一个错误,... 查看详情

基于filter实现gzip数据压缩

在web开发中,当服务器端向客户端返回的数据量比较大时,我们可以通过Gzip对数据进行压缩处理注意:如果小数据量进行压缩,压缩后的数据可能比原始数据还大;所以response返回数据量比较小时不推荐使用 这里代码结构,... 查看详情

springsecurity最难的地方就是这个了(代码片段)

本篇摘自胖哥最新的基于SpringSecurity5.6.x的《SpringSecurity干货》教程。旧版的教程将在2022年1月1日下线,请需要的同学尽快通过本公众号回复“2021开工福利”下载。原创不易,请多多关注、点赞、转发。SpringSecurity最难的... 查看详情

r语言dplyr包数据过滤(filter)基于notin规则实战(notinfilter):基于单数据列notin规则过滤数据行基于多数据列notin规则过滤数据行

R语言dplyr包数据过滤(filter)基于notin规则实战(notinFilter):基于单数据列notin规则过滤数据行、基于多数据列notin规则过滤数据行目录 查看详情

基于servlet实现一个web框架

...b开发框架,可是其webaction(响应某个URI的实现)的实现都是基于类的,不是非常方便,而且3.0之前的版本号还必须通过web.xml配置来添加新的action。servlet中有一个filter的功能,能够配置全部URI的功能都经过filter。我们能够基于filter... 查看详情

javaee系列--filter技术

Filter是JavaEE中的另一个重要部分,很多Web框架都是基于Filter实现的。1.什么是Filter?Filter是用来过滤请求资源和资源响应的对象(这里的资源指静态内容和Servlet等);  Filter接口定义了Filter的生命周期(由Web服务器管理)... 查看详情

springcloudgateway:基于serverwebexchange修改请求或者响应内容(代码片段)

SpringCloudGateway:基于ServerWebExchange修改请求或者响应内容前提本文编写的时候使用的SpringCloudGateway版本为当时最新的版本Greenwich.SR1。SpringCloudGateway同zuul类似,有“pre”和“post”两种方式的filter。客户端的请求先经过“pre... 查看详情

springcloudgateway:基于serverwebexchange修改请求或者响应内容(代码片段)

SpringCloudGateway:基于ServerWebExchange修改请求或者响应内容前提本文编写的时候使用的SpringCloudGateway版本为当时最新的版本Greenwich.SR1。SpringCloudGateway同zuul类似,有“pre”和“post”两种方式的filter。客户端的请求先经过“pre... 查看详情

servlet相关filter相关listener相关

...求/响应”的模式。Servlet可完成以下功能:1、创建并返回基于客户请求的动态HTML页面。2、创建可以嵌入到现有HTML页面中的HTML片段。3、与其它服务器资源(如数据库或基于Jav 查看详情

filter和interceptor的区别

一、filter基于filter接口中的doFilter回调函数,interceptor则基于Java本身的反射机制;二、filter是依赖于servlet容器的,没有servlet容器就无法回调doFilter方法,而interceptor与servlet无关;三、filter的过滤范围比interceptor大,filter除了过滤... 查看详情

angularjs之filter过滤器

  现在公司用ionic,就是基于angularjs封装了一些api用于webapp,最近用的angularjs的filter确实省了很多代码,现在总结一下!  ng比较鸡肋的过滤器,这里就一笔带过吧!鸡汤类常用的filter后面上例子。lowercase(小写){{lastName|lowerca... 查看详情

django: filter.values(...) 不检索请求的字段

...fieldsrequested【发布时间】:2020-11-1216:23:02【问题描述】:基于以下模型,我正在尝试通过执行以下过滤器查询BankingDetail并检索帐户和机会的信息:查询:bankingLST=BankingDetail.objects.filter( 查看详情

org.springframework.web.filter.HiddenHttpMethodFilter 不能转换为 javax.servlet.Filter

】org.springframework.web.filter.HiddenHttpMethodFilter不能转换为javax.servlet.Filter【英文标题】:org.springframework.web.filter.HiddenHttpMethodFiltercannotbecasttojavax.servlet.Filter【发布时间】:2012-11-2003:21:08【问题描述】:我有一个基于spring框架3 查看详情

orm多表查询基于双下划线(代码片段)

###########基于双下划线的跨表查询(基于join实现的)#############key:正向查询按字段,反向查询按表名小写1.查询python这本书出版社的名字ret=Book.objects.filter(title="python").values("publish__name")print(ret)ret=Publish.objects.filter(book__title="p 查看详情

图像处理基于matlabgui图像滤镜(马赛克+蓝光透镜+素描)含matlab源码1145期(代码片段)

一、简介基于matlabGUI图像滤镜(马赛克+蓝光透镜+素描)二、源代码%%2021/07/21functionvarargout=filter_pic(varargin)%FILTER_PICMATLABcodeforfilter_pic.fig%FILTER_PIC,byitself,createsanewFILTER_PICorraise 查看详情

基于springboot搭建java项目(二十三)——springboot使用过滤器拦截器和监听器(代码片段)

SpringBoot使用过滤器、拦截器和监听器一、SpringBoot使用过滤器Springboot过滤器的使用(两种方式)使用springboot提供的FilterRegistrationBean注册Filter使用原生servlet注解定义Filter两种方式的本质都是一样的,都是去FilterRegistrat... 查看详情

django queryset filter datetimefield

...15-02-0417:06:58【问题描述】:我很想将djangoquerysetAPI参考与基于DateTimeField的过滤器一起使用。我在我的models.py中准备了以下模型:classKleedkamerIndeling(models.Model):gametimedate=models.DateTimeField(auto_ 查看详情

覆盖 django admin List_Filter 模板

...templates【发布时间】:2015-03-1622:46:12【问题描述】:我想基于此覆盖默认的django管理过滤器模板以使用我自己的模板:https://github.com/feincms/feincms/blob/master/feincms/templates/admin/filter.html我通 查看详情