Skip to content

Spring WebMVC 集成

CoSec 通过 jakarta.servlet.Filter 实现为传统 Spring MVC 应用提供基于 Servlet 的集成路径。Servlet 集成与响应式 WebFlux 集成类似,但使用线程本地上下文传播代替 Reactor Context。

架构概览

mermaid
graph TD
    A["Incoming HTTP Request"] --> B["AuthorizationFilter<br>(jakarta.servlet.Filter)"]
    B --> C["AbstractAuthorizationInterceptor<br>(base class)"]
    C --> D["ServletRequestParser"]
    D --> E["CoSecServletRequest"]
    C --> F["SecurityContextParser"]
    F --> G["SecurityContext"]
    C --> H["Authorization.authorize()"]
    H --> I{Authorized?}
    I -->|Yes| J["FilterChain.doFilter()"]
    I -->|No| K["HTTP 401 / 403 Response"]
    J --> L["SecurityContextHolder<br>(ThreadLocal propagation)"]
    J --> M["ServletRequests<br>(request attribute propagation)"]

    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style G fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style H fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style I fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style J fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style K fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style M fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

核心组件

AuthorizationFilter

Servlet 过滤器入口点。它实现了 jakarta.servlet.Filter 并扩展了 AbstractAuthorizationInterceptor,对每个传入请求执行授权检查。

mermaid
sequenceDiagram
    autonumber
    participant Client
    participant Filter as AuthorizationFilter
    participant Interceptor as AbstractAuthorizationInterceptor
    participant RParser as ServletRequestParser
    participant SCParser as SecurityContextParser
    participant Auth as Authorization
    participant Chain as FilterChain

    Client->>Filter: doFilter(request, response, chain)
    Filter->>Interceptor: authorize(httpRequest, httpResponse)
    Interceptor->>RParser: parse(servletRequest)
    RParser-->>Interceptor: CoSecServletRequest
    Interceptor->>SCParser: parse(request)
    Note over Interceptor,SCParser: TokenVerificationException caught<br>falls back to anonymous context
    SCParser-->>Interceptor: SecurityContext
    Interceptor->>Auth: authorize(request, securityContext).block()
    Auth-->>Interceptor: AuthorizeResult
    alt Authorized
        Interceptor-->>Filter: true
        Filter->>Chain: chain.doFilter(request, response)
    else Not Authenticated
        Interceptor-->>Filter: false
        Filter-->>Client: HTTP 401 UNAUTHORIZED
    else Forbidden
        Interceptor-->>Filter: false
        Filter-->>Client: HTTP 403 FORBIDDEN
    end

AuthorizationFilter.doFilter 的关键行为:

  1. 委托AbstractAuthorizationInterceptor.authorize()
  2. 捕获 TooManyRequestsException 并返回 HTTP 429。
  3. 捕获意外异常,记录错误日志并返回 HTTP 500。
  4. 授权成功后,调用 chain.doFilter(request, response)

AbstractAuthorizationInterceptor

包含授权算法的基类。它与 ReactiveSecurityFilter 中的逻辑保持一致 -- 此处的任何更改都应同步到响应式对应类中。

authorize() 方法:

  1. 通过 ServletRequestParser 将 Servlet 请求解析为 CoSec Request
  2. 通过 SecurityContextParser 解析 SecurityContext,捕获 TokenVerificationException
  3. 将上下文存储到 SecurityContextHolder(线程本地)和请求属性中。
  4. 设置 X-Request-Id 响应头。
  5. 调用 authorization.authorize() 并阻塞等待结果(.block())。
  6. 如果被拒绝返回 false,如果被允许返回 true

ServletRequestParser

jakarta.servlet.http.HttpServletRequest 转换为 CoSecServletRequest,提取路径(通过 servletPath)、方法、远程 IP、来源、引用页和请求 ID。同时应用已注册的 RequestAttributesAppender 实例。

CoSecServletRequest

包装 HttpServletRequest 的不可变数据类。实现了 CoSec 的 Request 接口和 Delegated<HttpServletRequest>,提供对底层 Servlet 请求中 headers、查询参数和 cookies 的访问。

InjectSecurityContextFilter

ReactiveInjectSecurityContextWebFilter 的 Servlet 对应版本。专为已执行授权的 API 网关背后的下游服务设计。它使用 SecurityContextParser.ensureParse() 从请求头中提取安全上下文,无需令牌验证。

mermaid
graph TD
    subgraph "Gateway Service"
        A["AuthorizationFilter<br>(verifies JWT + authorizes)"]
    end
    subgraph "Downstream Service"
        B["InjectSecurityContextFilter<br>(injects context from headers)"]
        B --> C["SecurityContextHolder.setContext()"]
        B --> D["ServletRequests.setSecurityContext()"]
    end
    A -->|"X-Request-Id, Authorization headers"| B

    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

SecurityContextHolder

当前安全上下文的线程本地持有者。使用 InheritableThreadLocal 使子线程继承父线程的上下文。提供静态方法 setContext()contextrequiredContextremove()

上下文传播

与使用 Reactor Context 的响应式集成不同,Servlet 集成使用两个并行通道:

通道机制作用域
SecurityContextHolderInheritableThreadLocal当前线程和子线程
HttpServletRequest 属性request.setAttribute()当前请求生命周期

两者都在 AbstractAuthorizationInterceptor.authorize() 中设置,因此下游代码可以通过任一机制访问安全上下文。

参考资料

相关页面

基于 Apache License 2.0 发布