Class ApiKeyAuthenticationFilter

java.lang.Object
org.springframework.web.filter.GenericFilterBean
org.springframework.web.filter.OncePerRequestFilter
io.leandev.appfuse.security.auth.ApiKeyAuthenticationFilter
All Implemented Interfaces:
jakarta.servlet.Filter, org.springframework.beans.factory.Aware, org.springframework.beans.factory.BeanNameAware, org.springframework.beans.factory.DisposableBean, org.springframework.beans.factory.InitializingBean, org.springframework.context.EnvironmentAware, org.springframework.core.env.EnvironmentCapable, org.springframework.web.context.ServletContextAware

public class ApiKeyAuthenticationFilter extends org.springframework.web.filter.OncePerRequestFilter

API key 認證 filter(ADR-025 決策五)

做不了 token exchange 的外部系統以靜態 key 直接呼叫 API。它是同一個服務帳號身分的 第二種憑證呈遞,不是繞過既有機制的旁路——認證成功後放進 SecurityContext 的就是同一個 AuthPrincipal,因而走完全相同的權限鏈;租戶亦由既有的 UserDetailsTenantIdResolverCompositeTenantIdResolver.withDefaults() 已含)自主體解析, 不需要任何額外接線。

三條刻意的行為

① 無標頭即完全不介入。 不是本 filter 的請求原樣往下走(bearer token 路徑不受影響)。

② 驗證失敗不自己回 401,只是不設認證。 交由 chain 的 anyRequest().authenticated() 與既有的 bearer entry point 統一回應——錯誤形狀因而與其他認證失敗一致,且本 filter 不需要 知道消費端選了哪種錯誤方言。這仍是 fail-closed:不設認證 ≠ 放行。

③ 只接受非互動主體。 真人帳號不得以 api-key 呈遞(與 M2M token 端點的資格判定同一條線)。

撤銷

停用主體(enabled=false)或清除其 key 即刻失效——這正是相對 JWT 的優勢:無需等 token 過期,也不需要黑名單。每個請求都會重新查找與檢查。

組合

jar 不做 autoconfiguration;由消費端在 filter chain 註冊,且預設不註冊(ADR-025 決策五: 未設定即該路徑不存在)。應置於 bearer 驗證之前。

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    預設的 API key 請求標頭

    Fields inherited from class org.springframework.web.filter.OncePerRequestFilter

    ALREADY_FILTERED_SUFFIX

    Fields inherited from class org.springframework.web.filter.GenericFilterBean

    logger
  • Constructor Summary

    Constructors
    Constructor
    Description
    以預設標頭(DEFAULT_HEADER)建立
     
  • Method Summary

    Modifier and Type
    Method
    Description
    protected void
    doFilterInternal(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain)
     

    Methods inherited from class org.springframework.web.filter.OncePerRequestFilter

    doFilter, doFilterNestedErrorDispatch, getAlreadyFilteredAttributeName, isAsyncDispatch, isAsyncStarted, shouldNotFilter, shouldNotFilterAsyncDispatch, shouldNotFilterErrorDispatch

    Methods inherited from class org.springframework.web.filter.GenericFilterBean

    addRequiredProperty, afterPropertiesSet, createEnvironment, destroy, getEnvironment, getFilterConfig, getFilterName, getServletContext, init, initBeanWrapper, initFilterBean, setBeanName, setEnvironment, setServletContext

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

  • Constructor Details

    • ApiKeyAuthenticationFilter

      public ApiKeyAuthenticationFilter(ApiKeyPrincipalLookup principalLookup)
      以預設標頭(DEFAULT_HEADER)建立
      Parameters:
      principalLookup - key → 主體的查找 SPI
    • ApiKeyAuthenticationFilter

      public ApiKeyAuthenticationFilter(ApiKeyPrincipalLookup principalLookup, String headerName)
      Parameters:
      principalLookup - key → 主體的查找 SPI
      headerName - API key 的請求標頭名(消費端慣例)
  • Method Details

    • doFilterInternal

      protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException
      Specified by:
      doFilterInternal in class org.springframework.web.filter.OncePerRequestFilter
      Throws:
      jakarta.servlet.ServletException
      IOException