仓库地址
原理分析
Mirrors 路由配置与 AbstractGatewayFilterFactory
1. 路由全景图
flowchart TB
subgraph Client["客户端请求"]
BROWSER["🌐 浏览器<br/>(无 Authorization 头)"]
API_CLIENT["🔧 API 客户端<br/>(带 Authorization: Basic ... 头)"]
OAUTH_CB["🔄 OAuth2 回调<br/>(GitLab 重定向回 /oauth2-gitlab/callback)"]
end
subgraph SCG["Spring Cloud Gateway 路由匹配"]
direction TB
R1["路由 1: oauth2-gitlab-callback<br/>━━━━━━━━━━━━━━━━━━<br/>Path=/oauth2-gitlab/callback<br/>→ OAuth2AuthorizationCode<br/> uri: no://op"]
R2["路由 2: mirrors-basic<br/>━━━━━━━━━━━━━━━━━━<br/>Path=/service/rest/repository/browse/**<br/>Header=Authorization, Basic .+<br/>→ RewritePath → DelegateBasicAuth → StaticFileServer<br/> uri: no://op"]
R3["路由 3: mirrors-oauth2<br/>━━━━━━━━━━━━━━━━━━<br/>Path=/service/rest/repository/browse/**<br/>→ RewritePath → OAuth2AuthorizationCode → StaticFileServer<br/> uri: no://op"]
R1 --> R2 --> R3
end
BROWSER -->|"GET /service/rest/repository/browse/"| SCG
API_CLIENT -->|"GET /service/rest/repository/browse/<br/>Authorization: Basic base64(token:)"| SCG
OAUTH_CB -->|"GET /oauth2-gitlab/callback?code=...&state=..."| SCG
subgraph Legend["图例"]
L1["🔒 认证过滤器"]
L2["✂️ 路径重写"]
L3["📁 静态文件服务"]
end
style R1 fill:#ffe6cc,stroke:#d79b00
style R2 fill:#d5e8d4,stroke:#82b366
style R3 fill:#dae8fc,stroke:#6c8ebf
2. mirrors-basic 路由详细流程 (Basic Auth 委托)
sequenceDiagram
participant Client as API Client
participant GW as Spring Cloud Gateway
participant RP as RewritePath Filter
participant DBA as DelegateBasicAuth Filter
participant SFS as StaticFileServer Filter
participant GitLab as GitLab API<br/>(authUrl)
Client->>GW: GET /service/rest/repository/browse/kubernetes/<br/>Authorization: Basic base64(glpat-xxxx:)
Note over GW,RP: Route matched: mirrors-basic<br/>Predicates: Path + Header=Authorization: Basic .+
GW->>RP: 1️⃣ RewritePath
Note over RP: regexp: /service/rest/repository/browse/?(?<remaining>.*)<br/>replacement: /${remaining}<br/>→ path becomes /kubernetes/
GW->>DBA: 2️⃣ DelegateBasicAuth (order=-10)
Note over DBA: 提取 Authorization: Basic ...<br/>passDecodedOnly=true → Base64解码<br/>passPasswordOnly=true → 只取密码部分(glpat-xxxx)<br/>forwardHeaderName=PRIVATE-TOKEN
DBA->>GitLab: GET https://gitlab.xuxiaowei.com.cn/api/v4/user<br/>PRIVATE-TOKEN: glpat-xxxx
GitLab-->>DBA: 200 OK (包含 marker: "active")
Note over DBA: 检查响应体是否包含 marker<br/>✅ 认证通过 → 继续
GW->>SFS: 3️⃣ StaticFileServer (order=10)
Note over SFS: baseDirectory: ./blobs/<br/>查找文件: ./blobs/kubernetes/
SFS-->>Client: 200 OK — 目录列表 HTML 或文件内容
3. mirrors-oauth2 路由详细流程 (OAuth2 授权码)
sequenceDiagram
participant Browser as 浏览器
participant GW as Spring Cloud Gateway
participant RP as RewritePath Filter
participant OAuth as OAuth2AuthorizationCode Filter
participant GitLab as GitLab OAuth2
Browser->>GW: GET /service/rest/repository/browse/
Note over GW: Route matched: mirrors-oauth2<br/>(无 Authorization header, 由 mirrors-basic 漏下)
GW->>RP: 1️⃣ RewritePath (同上)
GW->>OAuth: 2️⃣ OAuth2AuthorizationCode (order=-10)
Note over OAuth: ❌ 无 oauth2_access_token cookie<br/>生成 32字节随机 state<br/>设置 cookie: oauth2_state + oauth2_original_url<br/>
OAuth-->>Browser: 302 → GitLab /oauth/authorize<br/>?response_type=code&client_id=...&redirect_uri=...&scope=read_user&state=...
Browser->>GitLab: GET /oauth/authorize (用户登录)
GitLab-->>Browser: 302 → /oauth2-gitlab/callback?code=...&state=...
Browser->>GW: GET /oauth2-gitlab/callback?code=...&state=...
Note over GW: Route matched: oauth2-gitlab-callback
GW->>OAuth: OAuth2AuthorizationCode
Note over OAuth: ✅ 验证 state<br/>POST tokenUrl → 获取 access_token<br/>GET userInfoUri → 验证 token 有效 (含 marker)<br/>AES-256-GCM 加密 access_token<br/>RSA 签名 JWT → 设置 cookie: oauth2_access_token<br/>清除 state + original_url cookie
OAuth-->>Browser: 302 → 原始 URL
Browser->>GW: GET /service/rest/repository/browse/ (再次)
GW->>RP: RewritePath
GW->>OAuth: OAuth2AuthorizationCode
Note over OAuth: ✅ 找到 oauth2_access_token cookie<br/>验证 JWT RSA 签名<br/>解密 access_token<br/>设置 Header: Authorization: Bearer <token><br/>
GW->>GW: StaticFileServer
Note over GW: 返回目录列表或文件
4. AbstractGatewayFilterFactory 类层次结构
classDiagram
class AbstractGatewayFilterFactory~T~ {
<<Spring Cloud Gateway>>
+apply(T config) GatewayFilter
#getConfigClass() Class~T~
+name() String
+shortcutFieldOrder() List~String~
}
class Ordered {
<<interface>>
+getOrder() int
}
class BasicAuthGatewayFilterFactory {
-username: String
-password: String
+apply(Config) GatewayFilter
+getOrder() -10
}
class DelegateBasicAuthGatewayFilterFactory {
-authUrl: String
-marker: String
-authHeaderName: String
-forwardHeaderName: String
-passDecodedOnly: boolean
-passPasswordOnly: boolean
-webClient: WebClient
+apply(Config) GatewayFilter
+getOrder() -10
}
class DelegateBearerAuthGatewayFilterFactory {
-authUrl: String
-marker: String
-authHeaderName: String
-forwardHeaderName: String
-webClient: WebClient
+apply(Config) GatewayFilter
+getOrder() -10
}
class OAuth2AuthorizationCodeGatewayFilterFactory {
-authorizeUrl: String
-tokenUrl: String
-clientId: String
-clientSecret: String
-redirectUri: String
-scopes: String
-userInfoUri: String
-jwtPrivateKey: RSAPrivateKey
-jwtPublicKey: RSAPublicKey
-encryptionSecret: String
-excludePatterns: List~String~
-webClient: WebClient
+apply(Config) GatewayFilter
+getOrder() -10
}
class StaticFileServerGatewayFilterFactory {
-baseDirectory: String
-docUrl: String
-docText: String
-description: String
+apply(Config) GatewayFilter
+getOrder() 10
}
class WriteResponseToFileGatewayFilterFactory {
-directory: String
-returnFilePath: boolean
-useDatePrefix: boolean
+apply(Config) GatewayFilter
+getOrder() WRITE_RESPONSE_FILTER_ORDER-1
}
AbstractGatewayFilterFactory~T~ <|-- BasicAuthGatewayFilterFactory
AbstractGatewayFilterFactory~T~ <|-- DelegateBasicAuthGatewayFilterFactory
AbstractGatewayFilterFactory~T~ <|-- DelegateBearerAuthGatewayFilterFactory
AbstractGatewayFilterFactory~T~ <|-- OAuth2AuthorizationCodeGatewayFilterFactory
AbstractGatewayFilterFactory~T~ <|-- StaticFileServerGatewayFilterFactory
AbstractGatewayFilterFactory~T~ <|-- WriteResponseToFileGatewayFilterFactory
Ordered <|.. BasicAuthGatewayFilterFactory
Ordered <|.. DelegateBasicAuthGatewayFilterFactory
Ordered <|.. DelegateBearerAuthGatewayFilterFactory
Ordered <|.. OAuth2AuthorizationCodeGatewayFilterFactory
Ordered <|.. StaticFileServerGatewayFilterFactory
Ordered <|.. WriteResponseToFileGatewayFilterFactory
note for AbstractGatewayFilterFactory~T~ "泛型参数 T 是内部 Config 类<br/>通过 shortcutFieldOrder() 支持<br/>YAML 简化参数传递"
5. AbstractGatewayFilterFactory 核心原理
%%{init: {'flowchart': {'wrappingWidth': 450, 'nodeSpacing': 25, 'rankSpacing': 50}}}%%
flowchart TB
subgraph Config["🟡 YAML 配置 (application-mirrors.yaml)"]
YAML["filters:<br/> - name: DelegateBasicAuth<br/> args:<br/> authUrl: https://...<br/> marker: active"]
end
subgraph Loading["🟢 Spring Cloud Gateway 加载过程"]
L1["1. GatewayAutoConfiguration 扫描所有<br/>GatewayFilterFactory Bean"]
L2["2. RouteDefinitionRouteLocator 解析<br/>RouteDefinition"]
L3["3. 根据 filter name 查找对应的<br/>GatewayFilterFactory Bean"]
L4["4. 调用 normalize() → shortcutFieldOrder()<br/>将 args Map 按序映射到 Config 对象"]
L5["5. 调用 apply(Config) 返回 GatewayFilter"]
end
subgraph Runtime["🔵 请求处理流程"]
R1["请求到达 Gateway"]
R2["RoutePredicateHandlerMapping<br/>匹配路由 predicates"]
R3["FilteringWebHandler 构建<br/>Filter Chain"]
R4["按 Order 排序所有 GatewayFilter<br/>(包括 GlobalFilter)"]
R5["链式调用 filter(ServerWebExchange, GatewayFilterChain)"]
end
subgraph FilterChain["🟣 OrderedGatewayFilter 包装"]
FC1["AbstractGatewayFilterFactory.apply()<br/>内部调用 OrderedGatewayFilter 包装"]
FC2["将实现的 Ordered.getOrder()<br/>传递给 OrderedGatewayFilter"]
FC3["确保 filter chain 按 order 值<br/>从小到大依次执行"]
end
subgraph Execution["🔴 过滤器执行顺序"]
E1["LogWebFilter<br/>(order=HIGHEST_PRECEDENCE)<br/>↓"]
E2["认证过滤器<br/>BasicAuth / DelegateBasicAuth<br/>DelegateBearer / OAuth2AuthorizationCode<br/>(order=-10)<br/>↓"]
E3["业务过滤器<br/>StaticFileServer / WriteResponseToFile<br/>(order=10 / WRITE_RESPONSE-1)<br/>↓"]
E4["NettyRoutingFilter<br/>(实际 HTTP 请求 — 本例 uri=no://op 跳过)"]
end
Config --> Loading
Loading --> Runtime
Runtime --> FilterChain
FilterChain --> Execution
style Config fill:#fff2cc,stroke:#d6b656
style Loading fill:#d5e8d4,stroke:#82b366
style Runtime fill:#dae8fc,stroke:#6c8ebf
style FilterChain fill:#e1d5e7,stroke:#9673a6
style Execution fill:#f8cecc,stroke:#b85450
6. YAML 配置到运行时对象的映射原理
%%{init: {'flowchart': {'wrappingWidth': 450, 'nodeSpacing': 25, 'rankSpacing': 50}}}%%
flowchart LR
subgraph YAML["application-mirrors.yaml"]
Y1["name: DelegateBasicAuth<br/>args:<br/> authUrl: https://...<br/> marker: active<br/> authHeaderName: Authorization<br/> forwardHeaderName: PRIVATE-TOKEN<br/> passDecodedOnly: true<br/> passPasswordOnly: true"]
end
subgraph NORM["normalize() 方法"]
N1["1. 读取 shortcutFieldOrder()<br/> = ['authUrl', 'marker']"]
N2["2. 若 args 是 Map:<br/> 按 key 直接映射到 Config 字段"]
N3["3. 若 args 是 List:<br/> 按 shortcutFieldOrder 索引<br/> 依次映射到对应字段"]
N4["4. 类型转换 (String→boolean 等)"]
end
subgraph CONFIG["Config 对象 (内部类)"]
C1["authUrl = 'https://...'<br/>marker = 'active'<br/>authHeaderName = 'Authorization'<br/>forwardHeaderName = 'PRIVATE-TOKEN'<br/>passDecodedOnly = true<br/>passPasswordOnly = true"]
end
subgraph FILTER["apply(Config)"]
F1["return OrderedGatewayFilter(<br/> (exchange, chain) → {<br/> // 1. 提取认证头<br/> // 2. 调用外部服务验证<br/> // 3. 成功→chain.filter()<br/> // 4. 失败→401<br/> },<br/> getOrder() = -10<br/>)"]
end
YAML --> NORM --> CONFIG --> FILTER
style YAML fill:#fff2cc,stroke:#d6b656
style NORM fill:#d5e8d4,stroke:#82b366
style CONFIG fill:#dae8fc,stroke:#6c8ebf
style FILTER fill:#f8cecc,stroke:#b85450
7. 三种认证方式对比
%%{init: {'flowchart': {'wrappingWidth': 450, 'nodeSpacing': 25, 'rankSpacing': 50}}}%%
flowchart LR
subgraph BA["🔐 DelegateBasicAuth"]
direction TB
BA1["输入: Authorization: Basic base64(user:pass)"]
BA2["1. 提取 header → Base64 解码"]
BA3["2. passDecodedOnly? → 只用解码后的值"]
BA4["3. passPasswordOnly? → 只取密码部分 (:后)"]
BA5["4. 设置 forwardHeaderName=PRIVATE-TOKEN"]
BA6["5. GET authUrl → 检查响应体 marker"]
BA7["适用: GitLab Personal Access Token"]
BA1 --> BA2 --> BA3 --> BA4 --> BA5 --> BA6 --> BA7
end
subgraph DBA["🔐 DelegateBearerAuth"]
direction TB
DBA1["输入: Authorization: Bearer <token>"]
DBA2["1. 提取 header → 取 Bearer 后的 token"]
DBA3["2. 设置 forwardHeaderName=Authorization"]
DBA4["3. GET authUrl → 检查响应体 marker"]
DBA5["适用: OAuth2 Bearer Token 内省"]
DBA1 --> DBA2 --> DBA3 --> DBA4 --> DBA5
end
subgraph OAuth["🔐 OAuth2AuthorizationCode"]
direction TB
OA1["无有效 token → 302 重定向到授权页"]
OA2["回调 → 用 code 换 token"]
OA3["token 加密后存 JWT cookie"]
OA4["后续请求 → 解密 JWT 获取 token"]
OA5["设置 Authorization: Bearer <token>"]
OA6["支持 excludePatterns 白名单"]
OA7["适用: 浏览器用户 (交互式登录)"]
OA1 --> OA2 --> OA3 --> OA4 --> OA5 --> OA6 --> OA7
end
BA ~~~ DBA ~~~ OAuth
style BA fill:#d5e8d4,stroke:#82b366
style DBA fill:#dae8fc,stroke:#6c8ebf
style OAuth fill:#e1d5e7,stroke:#9673a6
总结
路由匹配策略
oauth2-gitlab-callback— 精确匹配 OAuth2 回调路径,由 GitLab 重定向触发mirrors-basic— 同时匹配路径 和Authorization: Basic头,为 API 客户端提供 Basic Auth 委托mirrors-oauth2— 只匹配路径(Basic 路由匹配不上时漏下),为浏览器用户提供 OAuth2 交互式登录
AbstractGatewayFilterFactory 核心机制
name()返回 Bean 名(如DelegateBasicAuth),YAML 中通过 name 引用shortcutFieldOrder()支持 YAML 中 args 的简写形式apply(Config)是核心方法,返回GatewayFilter函数式接口Ordered接口配合OrderedGatewayFilter控制过滤器链执行顺序OrderedGatewayFilter是 Spring Cloud Gateway 的内部类,将Ordered的 order 值注入到过滤器链排序中no://op表示不转发到真实后端,响应由过滤器直接生成
真实使用场景
yum/rpm 仓库增加权限认证
支持 OAuth2 authorization_code 协议:用于浏览器访问
- 支持白名单路径
支持 Basic 协议:用于rpm 仓库认证
支持代理本地(静态)文件