在 Spring Security 中自定义一个的过滤器,将其添加到 Spring Security 过滤器链的合适位置。定义一个自己的过滤器类继承 Filter 接口即可。
但是在 Spring 体系中,推荐使用 OncePerRequestFilter 来实现,它可以确保一次请求只会通过一次该过滤器(Filter 实际上并不能保证这 一点)。
public class MySecurityFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
// 非登录请求,不处理
if("/login".equals(httpServletRequest.getRequestURI())&&httpServletRequest.getMethod().equals(HttpMethod.POST.name())) {
String username = httpServletRequest.getParameter("username");
String password = httpServletRequest.getParameter("password");
System.out.println("username:" + username);
System.out.println("password:" + password);
}else {
System.out.println("非登录处理!");
}
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
创建 Spring Security 配置类,继承WebSecurityConfigurerAdapter
, 重写方法void configure(HttpSecurity http)
, 将自定义的过滤器添加到 Spring Security 过滤器链中:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// 将自定义的过滤器添加到Spring Security 过滤器链中
http.addFilterBefore(new MySecurityFilter(),UsernamePasswordAuthenticationFilter.class);
}
}
将该过滤器添加到 Spring Security 的过滤器链中即可生效, Spring Security 支持三种 filter 添加策略:
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
......
// 将自定义的过滤器添加在指定过滤器之后
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
this.comparator.registerAfter(filter.getClass(), afterFilter);
return this.addFilter(filter);
}
// 将自定义的过滤器添加在指定过滤器之前
public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
this.comparator.registerBefore(filter.getClass(), beforeFilter);
return this.addFilter(filter);
}
// 添加一个过滤器,但必须是Spring Security自身提供的过滤器实例或其子过滤器
public HttpSecurity addFilter(Filter filter) {
Class<? extends Filter> filterClass = filter.getClass();
if (!this.comparator.isRegistered(filterClass)) {
throw new IllegalArgumentException("The Filter class " + filterClass.getName() + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
} else {
this.filters.add(filter);
return this;
}
}
// 添加一个过滤器在指定过滤器位置
public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
this.comparator.registerAt(filter.getClass(), atFilter);
return this.addFilter(filter);
}
......
}
重启服务测试:
访问localhost:8080/login
, 会跳转到 localhost:8080/login.html 页面,输入账号密码, 登录, 整个流程的日志记录如下:
非登录处理!
username:admin
password:aaaaaa
非登录处理!
实战:实现图片验证码
参考:kaptcha 谷歌验证码工具 https://www.cnblogs.com/zhangyuanbo/p/11214078.html
maven 引入验证码相关包
<!-- 图片验证码相关-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
获取图片验证码
编写自定义的图片验证码校验过滤器:
@Bean
public DefaultKaptcha getDDefaultKaptcha() {
DefaultKaptcha dk = new DefaultKaptcha();
Properties properties = new Properties();
// 图片边框
properties.setProperty("kaptcha.border", "yes");
// 边框颜色
properties.setProperty("kaptcha.border.color", "105,179,90");
// 字体颜色
properties.setProperty("kaptcha.textproducer.font.color", "red");
// 图片宽
properties.setProperty("kaptcha.image.width", "110");
// 图片高
properties.setProperty("kaptcha.image.height", "40");
// 字体大小
properties.setProperty("kaptcha.textproducer.font.size", "30");
// session key
properties.setProperty("kaptcha.session.key", "code");
// 验证码长度
properties.setProperty("kaptcha.textproducer.char.length", "4");
// 字体
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
dk.setConfig(config);
return dk;
}
KaptchaController.java
@Controller
public class KaptchaController {
/**
* 验证码工具
*/
@Autowired
DefaultKaptcha defaultKaptcha;
@GetMapping("/kaptcha.jpg")
public void defaultKaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 设置内容类型
response.setContentType("image/jpeg");
// 创建验证码文本
String createText = defaultKaptcha.createText();
// 将生成的验证码保存在session中
request.getSession().setAttribute("kaptcha", createText);
// 创建验证码图片
BufferedImage bi = defaultKaptcha.createImage(createText);
// 获取响应输出流
ServletOutputStream out = response.getOutputStream();
// 将图片验证码数据写入到图片输出流
ImageIO.write(bi, "jpg", out);
// 推送并关闭输出流
out.flush();
out.close();
}
}
当用户访问/captcha.jpg
时,即可得到一张携带验证码的图片,验证码文本则被存放到 session 中,用于后续的校验。
图片验证码校验过滤器
有了图形验证码的 API 之后,就可以自定义验证码校验过滤器。
public class MyVerificationCodeFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 只处理登录请求
if("/login".equals(request.getRequestURI())&&request.getMethod().equals(HttpMethod.POST.name())) {
if(this.verificationCode(request, response)){
filterChain.doFilter(request, response);
}else {
response.getWriter().write("verification code check failure!");
}
}else {
filterChain.doFilter(request, response);
}
}
private Boolean verificationCode(HttpServletRequest request,HttpServletResponse response){
// 从session中获取正确的验证码
HttpSession session = request.getSession();
String kaptcha = (String) session.getAttribute("kaptcha");
// 从参数中获取用户输入的验证码
String code = request.getParameter("code");
if (StringUtils.isEmpty(code)){
// 清空session中的验证码,让用户重新获取
session.removeAttribute("kaptcha");
return false;
}
// 验证码校验
if (!code.equals(kaptcha)){
return false;
}
return true;
}
}
将MyVerificationCodeFilter
添加在UsernamePasswordAuthenticationFilter
之前,即在密码认证之前:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// 将自定义的过滤器添加到Spring Security 过滤器链中
http
.addFilterBefore(new MyVerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);
}
自定义带验证码的登陆页
在 static 文件夹下新建 login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/login">
<input type="text" /><br/>
<input type="password" /><br/>
<div style="display: inline-block">
<input type="text" />
<img alt="验证码" onclick="this.src='/kaptcha.jpg'" src="/kaptcha.jpg" />
<a>看不清?点击图片刷新一下</a>
</div>
</br>
<input type="submit" value="登录">
</form>
</body>
</html>
修改WebSecurityConfig
,设置自定义登录页 URL:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http
.authorizeRequests()
.antMatchers("/kaptcha.jpg").permitAll() // 放开验证码获取的访问地址
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login.html") // 自定义登录页URL
.loginProcessingUrl("/login") // 自定义登陆处理请求地址
.permitAll();
// 将自定义的过滤器添加到Spring Security 过滤器链中
http
.addFilterBefore(new MyVerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);
}
}