在Spring Security中自定义一个的过滤器,将其添加到Spring Security过滤器链的合适位置。定义一个本身的过滤器类继承Filter接口便可。html
可是在 Spring 体系中,推荐使用
OncePerRequestFilter来实现,它能够确保一次请求只会经过一次该过滤器(Filter实际上并不能保证这
一点)。java
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 过滤器链中:git
@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添加策略:github
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页面,输入帐号密码,登陆,整个流程的日志记录以下:session
非登陆处理! username:admin password:aaaaaa 非登陆处理!
实战:实现图片验证码
参考:kaptcha谷歌验证码工具 https://www.cnblogs.com/zhangyuanbo/p/11214078.htmlapp
maven引入验证码相关包
<!-- 图片验证码相关--> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>
获取图片验证码
编写自定义的图片验证码校验过滤器:maven
@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.javaide
@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以后,就能够自定义验证码校验过滤器。post
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" name="username"/><br/> <input type="password" name="password"/><br/> <div style="display: inline-block"> <input type="text" name="code" required placeholder="验证码" /> <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); } }