在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); } }