Spring Security:為特定URL模式配置JWT過濾器

Spring Security:為特定URL模式配置JWT過濾器

本教程詳細講解如何在spring Boot Security中,精確控制JWT(json Web Token)過濾器的應用范圍,使其僅作用于指定的URL模式,而非全局生效。通過繼承AbstractAuthenticationProcessingFilter并結合RequestMatcher接口,開發者可以靈活定義哪些請求路徑需要JWT認證,從而優化安全策略,避免不必要的性能開銷,并增強應用的模塊化安全性。文章將提供詳細的代碼示例和配置步驟,幫助讀者實現定制化的安全過濾邏輯。

spring security中,我們經常需要自定義過濾器來處理特定的認證或授權邏輯,例如jwt認證。然而,默認情況下,通過httpsecurity.addfilterbefore()或addfilterat()添加的過濾器會作用于所有進來的http請求。對于jwt認證而言,通常我們只希望它對受保護的api路徑(例如/api/**)生效,而對靜態資源、登錄頁面或公共接口則無需進行jwt驗證。本文將介紹如何利用spring security提供的abstractauthenticationprocessingfilter和requestmatcher接口,實現jwt過濾器的精確控制。

挑戰:全局過濾器與局部需求

當我們將一個自定義JWT過濾器(例如CustomJwtAuthenticationFilter)通過以下方式添加到安全鏈中時:

http.addFilterBefore(customJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

這個customJwtAuthenticationFilter將會在UsernamePasswordAuthenticationFilter之前,對所有進入應用的請求進行處理。這意味著即使是訪問/login、/或任何非API路徑,該過濾器也會被觸發,這不僅可能導致不必要的性能開銷,還可能在某些情況下拋出異常(例如,嘗試從沒有JWT的請求頭中解析令牌)。

解決方案:AbstractAuthenticationProcessingFilter與RequestMatcher

Spring Security提供了一個抽象類AbstractAuthenticationProcessingFilter,它專門用于處理基于請求匹配的認證流程。這個類的核心在于其構造函數可以接收一個RequestMatcher對象。當一個請求到達時,AbstractAuthenticationProcessingFilter會首先調用其內部的RequestMatcher的matches()方法。只有當matches()方法返回true時,過濾器才會繼續執行其認證邏輯(即調用attemptAuthentication()方法);否則,它會直接跳過認證過程,將請求傳遞給安全鏈中的下一個過濾器。

RequestMatcher是一個接口,它定義了如何根據HttpServletRequest來判斷一個請求是否匹配特定條件。Spring Security提供了多種RequestMatcher的實現,其中最常用的是:

  • AntPathRequestMatcher:基于Ant風格路徑模式匹配URL。
  • OrRequestMatcher:將多個RequestMatcher組合,只要有一個匹配就返回true。
  • AndRequestMatcher:將多個RequestMatcher組合,只有所有都匹配才返回true。
  • NegatedRequestMatcher:對另一個RequestMatcher的結果取反。

實現步驟

我們將通過以下步驟實現JWT過濾器的精確控制:

1. 改造 CustomJwtAuthenticationFilter

讓你的JWT認證過濾器繼承AbstractAuthenticationProcessingFilter,并在構造函數中接收RequestMatcher和AuthenticationManager。

import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.RequestMatcher;  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  /**  * 自定義JWT認證過濾器,僅對匹配特定RequestMatcher的請求進行處理。  */ public class CustomJwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {      /**      * 構造函數。      * @param requiresAuthenticationRequestMatcher 定義哪些請求需要此過濾器處理的RequestMatcher      * @param authenticationManager 認證管理器,用于執行認證邏輯      */     public CustomJwtAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) {         super(requiresAuthenticationRequestMatcher); // 將RequestMatcher傳遞給父類         setAuthenticationManager(authenticationManager); // 設置認證管理器     }      /**      * 實現JWT認證的核心邏輯。      * 只有當RequestMatcher匹配時,此方法才會被調用。      */     @Override     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)             throws AuthenticationException, IOException, ServletException {         // 在這里實現你的JWT解析和認證邏輯         // 例如:從請求頭中獲取JWT令牌         String authorizationHeader = request.getHeader("Authorization");         if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {             // 如果沒有Bearer Token,拋出認證異常,或返回null讓后續認證機制處理             throw new AuthenticationException("Missing or invalid JWT token in Authorization header") {};         }          String jwtToken = authorizationHeader.substring(7); // 提取JWT字符串          // TODO: 根據你的JWT庫和業務邏輯驗證jwtToken,并構建一個Authentication對象         // 例如:         // JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(jwtToken);         // return getAuthenticationManager().authenticate(authenticationToken); // 委托給AuthenticationManager進行認證          // 示例:此處僅為演示,實際應替換為你的JWT驗證邏輯         System.out.println("Processing JWT for path: " + request.getRequestURI());         // 假設成功驗證并返回一個Authentication對象         // return new UsernamePasswordAuthenticationToken("user", null, Collections.emptyList());         throw new UnsupportedOperationException("JWT認證邏輯待實現,請替換為實際的令牌驗證和用戶身份構建。");     }      // 可選:重寫successfulAuthentication和unsuccessfulAuthentication方法來處理認證成功或失敗后的邏輯     // @Override     // protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {     //     super.successfulAuthentication(request, response, chain, authResult);     //     // 認證成功后繼續過濾器鏈     //     chain.doFilter(request, response);     // }      // @Override     // protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {     //     // 認證失敗處理,例如返回401 Unauthorized     //     response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);     //     response.getWriter().write("Authentication Failed: " + failed.getMessage());     // } }

2. 定義 RequestMatcher

針對“只過濾/api/**路徑”的需求,我們可以使用AntPathRequestMatcher。

import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;  // 簡單匹配單個路徑模式 // RequestMatcher apiRequestMatcher = new AntPathRequestMatcher("/api/**");  // 如果需要匹配多個路徑模式,可以使用OrRequestMatcher // RequestMatcher multiPathMatcher = new OrRequestMatcher( //     new AntPathRequestMatcher("/api/v1/**"), //     new AntPathRequestMatcher("/secure/**") // );

3. 配置 Spring Security

在你的安全配置類(通常是繼承WebSecurityConfigurerAdapter或使用SecurityFilterChain)中,將改造后的CustomJwtAuthenticationFilter作為Bean注入,并添加到安全鏈中。

 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;  @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {      // 假設你有一個JwtAuthenticationEntryPoint處理認證失敗的入口點     // @Autowired     // private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;      // 假設你有一個UserDetailsService用于加載用戶詳情(如果JWT認證需要)     // @Autowired     // private UserDetailsService userDetailsService;      /**      * 配置HTTP安全策略。      */     @Override     protected void configure(HttpSecurity http) throws Exception {         http.csrf().disable() // 禁用CSRF,因為JWT是無狀態的             .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 設置會話管理為無狀態             .and()             // .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and() // 配置認證入口點處理未認證請求             .authorizeRequests()             .antMatchers("/api/**").authenticated() // 明確指定 /api/** 路徑需要認證             .anyRequest().permitAll() // 其他所有請求都允許訪問             .and()             // 將我們定制的JWT過濾器添加到UsernamePasswordAuthenticationFilter之前             .addFilterBefore(customJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);     }      /**      * 將CustomJwtAuthenticationFilter注冊為Spring Bean。      * 注意:這里需要捕獲AuthenticationManagerBean()拋出的異常。

? 版權聲明
THE END
喜歡就支持一下吧
點贊6 分享