Apache Druid 认证与授权配置指南:Authenticator 链、Authorizer 与 Escalator 深度解析
Apache Druid 认证与授权配置指南:Authenticator 链、Authorizer 与 Escalator 深度解析
本文以 Apache Druid 官方运维文档 docs/operations/auth.md 为骨架,系统讲解 Druid 中与扩展无关(non-extension specific)的认证(Authentication)与授权(Authorization)基础配置。你将掌握 druid.auth.authenticatorChain、druid.auth.authorizers、druid.escalator.type 等核心属性的含义与用法,理解三种内置 Authenticator(AllowAll、Anonymous、Trusted Domain)的适用场景,以及 Druid 进程间内部通信如何通过 Escalator 与"内部系统用户"完成提权认证;文章同时结合 server 模块源码揭示这些配置背后的接口契约与 Filter 级实现原理,为后续接入 druid-basic-security、druid-kerberos 等安全扩展打下基础。
核心配置总览
Druid 的认证/授权基础能力完全通过 JVM 配置属性开启,与具体的认证方案(Basic、Kerberos、LDAP 等)解耦——具体认证方案由扩展提供。以下是 docs/operations/auth.md 给出的核心配置属性:
| 属性 | 类型 | 说明 | 默认值 | 是否必填 |
|---|---|---|---|---|
druid.auth.authenticatorChain |
JSON 字符串列表 | Authenticator 类型名列表,定义认证链 | ["allowAll"] |
否 |
druid.escalator.type |
String | 用于 Druid 内部通信的 Escalator 类型。该 Escalator 使用的认证方案必须被 druid.auth.authenticatorChain 中某个 Authenticator 支持 |
"noop" |
否 |
druid.auth.authorizers |
JSON 字符串列表 | Authorizer 类型名列表,定义授权器 | ["allowAll"] |
否 |
druid.auth.unsecuredPaths |
字符串列表 | 不做安全校验的路径列表,对这些路径的所有请求直接放行 | [] |
否 |
druid.auth.allowUnauthenticatedHttpOptions |
Boolean | 若为 true,允许未认证用户发送 HTTP OPTIONS 请求。主要用于支持 CORS 预检请求(Druid 本身不直接支持 CORS,可通过第三方扩展开启) |
false |
否 |
关于 druid.auth.allowUnauthenticatedHttpOptions,有两个必须注意的配套条件与安全影响:
- 必须同时在
druid.server.http.allowedHttpMethods中加入"OPTIONS",否则放行逻辑无法生效; - 关闭 OPTIONS 请求的认证检查后,未认证用户可以通过判断 OPTIONS 请求返回 200 而非 404 来探测 Druid 有效端点,进而泄露服务器配置信息(包括加载了哪些扩展),官方文档明确提示
Enabling this option will reveal information about server configuration,开启需谨慎。
druid.auth.unsecuredPaths 则常用于对健康检查、状态页等端点放行,例如 ["/status/health", "/status"],使其不进入认证/授权流程。
Authenticator 认证链工作原理
认证决策由一串 Authenticator 实例(chain)按序处理,顺序由 druid.auth.authenticatorChain 定义。具体实现由扩展提供,例如启用 druid-kerberos 与 druid-basic-security 两个核心扩展中的 Kerberos 与 HTTP Basic 认证器:
druid.auth.authenticatorChain=["kerberos", "basic"]
认证链的请求处理遵循以下规则:
- 请求依次经过链上所有 Authenticator,直到某个 Authenticator 成功完成认证,或某个 Authenticator 直接返回 HTTP 错误响应为止;
- 一旦出现首次认证成功或错误响应,链上后续 Authenticator 将被跳过;
- 若整个链走完仍无人成功认证、也无错误响应,则在链尾统一发送 HTTP 错误响应。
从源码层面看,Authenticator 接口本身继承自 ServletFilterHolder,其 getFilter() 返回的正是执行认证检查的 Servlet Filter(见 Authenticator.java)。接口契约在 Authenticator.java 中明确了三种情形:
- 认证成功:在请求上设置
Druid-Authentication-Result属性,内容为携带请求者身份与 Authorizer 名称的AuthenticationResult; - 请求格式无法识别:Filter 不应发送错误响应,将机会留给链上后续 Filter,若整个链耗尽则发送 challenge 响应;
- 认证失败(识别了方案但凭据错误):Filter 应直接发送错误响应,不再进入后续 Filter。
同时接口还定义了 getAuthChallengeHeader()(返回如 Basic、Negotiate 这样的 WWW-Authenticate 挑战头)与 authenticateJDBCContext()(用于对 Druid SQL 的 JDBC 连接上下文做认证)两个扩展点(Authenticator.java)。
Druid 内置三个 Authenticator 实现,类型名常量定义在 AuthConfig.java:allowAll、anonymous、trustedDomain,并通过 @JsonSubTypes 注册到 Authenticator 接口上(Authenticator.java)。
AllowAll 认证器
内置的 allowAll 认证器对所有请求一律认证通过,并总是将请求路由到名为 allowAll 的 Authorizer。官方文档明确指出它只应用于默认的未安全配置(unsecured configuration),不要在生产安全环境中使用。
druid.auth.authorizer.allowAll.type=allowAll
源码佐证:AllowAllAuthenticator.java 中定义了静态常量 ALLOW_ALL_RESULT,其身份、Authorizer 名称均为 "allowAll";其 Filter 的 doFilter 逻辑只是无条件设置 Druid-Authentication-Result 属性后放行(AllowAllAuthenticator.java)。
Anonymous 认证器
内置的 anonymous 认证器同样认证所有请求,但将请求路由到用户配置指定的 Authorizer,适用于为集群增加一个"默认访问级别"。官方建议将其放在认证链的末尾:当请求到达链尾的 Anonymous 认证器时,能否成功取决于其所关联 Authorizer 的授权结果。
其配置属性如下:
| 属性 | 说明 | 默认值 | 是否必填 |
|---|---|---|---|
druid.auth.authenticator.<authenticatorName>.authorizerName |
请求应被路由到的 Authorizer 名称 | N/A | 是 |
druid.auth.authenticator.<authenticatorName>.identity |
请求者的身份标识 | defaultUser |
否 |
使用方式是在认证链中加入类型为 anonymous 的认证器。例如配合 druid-basic-security 扩展(extensions-core/druid-basic-security):
druid.auth.authenticatorChain=["basic", "anonymous"]
druid.auth.authenticator.anonymous.type=anonymous
druid.auth.authenticator.anonymous.identity=defaultUser
druid.auth.authenticator.anonymous.authorizerName=myBasicAuthorizer
# ... usual configs for basic authentication would go here ...
源码佐证:AnonymousAuthenticator.java 中 DEFAULT_IDENTITY 为 "defaultUser",构造函数通过 @JsonProperty 接收 name、authorizerName、identity,identity 缺省时使用 defaultUser;其 Filter 无条件设置 AuthenticationResult 后放行。
Trusted Domain 认证器
内置的 trustedDomain 认证器用于信任同一域内主机:当请求来源 IP 属于配置的可信域时认证通过,并路由到用户指定的 Authorizer。适合为同域内主机提供默认信任级别。
其配置属性如下:
| 属性 | 说明 | 默认值 | 是否必填 |
|---|---|---|---|
druid.auth.authenticator.<authenticatorName>.name |
认证器名称 | N/A | 是 |
druid.auth.authenticator.<authenticatorName>.domain |
可信域,请求来源需属于该域才被认证。若只允许来自某台主机的连接,需填写该主机的完整域名(FQDN) | N/A | 是 |
druid.auth.authenticator.<authenticatorName>.useForwardedHeaders |
客户端可能经过多层代理,部分代理会在转发前把自身 IP 追加进 X-Forwarded-For 头。设为 true 时,若存在 X-Forwarded-For,则使用其中最左侧的主机名进行域匹配。注意:HTTP 请求中的 X-Forwarded-For 头可被伪造,启用需谨慎 |
false |
否 |
druid.auth.authenticator.<authenticatorName>.authorizerName |
请求应被路由到的 Authorizer 名称 | N/A | 是 |
druid.auth.authenticator.<authenticatorName>.identity |
请求者的身份标识 | defaultUser |
否 |
使用方式是在认证链中加入类型为 trustedDomain 的认证器:
druid.auth.authenticatorChain=["trustedDomain"]
druid.auth.authenticator.trustedDomain.type=trustedDomain
druid.auth.authenticator.trustedDomain.domain=trustedhost.mycompany.com
druid.auth.authenticator.trustedDomain.identity=defaultUser
druid.auth.authenticator.trustedDomain.authorizerName=myBasicAuthorizer
druid.auth.authenticator.trustedDomain.name=myTrustedAutenticator
# ... usual configs for druid would go here ...
源码实现细节(TrustedDomainAuthenticator.java):
- 构造函数用
Preconditions.checkArgument(!Strings.isNullOrEmpty(domain), ...)强制要求domain非空(TrustedDomainAuthenticator.java); - 域匹配逻辑是
remoteAddr.endsWith(domain),即请求来源地址以配置域名为后缀即视为可信(TrustedDomainAuthenticator.java); - 启用
useForwardedHeaders时,从X-Forwarded-For头取最左侧(原始客户端)地址,处理方式为forwarded_for.split(",")<a href="https://link.gitcode.com/i/4ffa8c53bbe9ef038f115d45270c1a8c" target="_blank">0]([TrustedDomainAuthenticator.java); - 匹配失败时不会设置认证结果属性,而是继续走 Filter 链(把机会留给链上后续认证器),认证器本身不发送错误响应(TrustedDomainAuthenticator.java)。
对应单元测试 TrustedDomainAuthenticatorTest.java 覆盖了四类场景,可据此验证行为:
testTrustedHost:来源地址serverA.test.com匹配域test.com,认证成功并写入AuthenticationResult(TrustedDomainAuthenticatorTest.java);testNonTrustedHost:来源地址serverA.test2.com不匹配,不写入认证结果(TrustedDomainAuthenticatorTest.java);testForwardedForTrustedHost:启用useForwardedHeaders后,即使直连地址不匹配,只要X-Forwarded-For中原始地址匹配也可认证(TrustedDomainAuthenticatorTest.java);testForwardedForTrustedHostMultiProxy:多级代理场景下取X-Forwarded-For最左侧地址(serverA.test.com,proxy-1,proxy-2中的serverA.test.com)进行匹配(TrustedDomainAuthenticatorTest.java)。
Escalator:内部集群通信的认证提升
druid.escalator.type 决定 Druid 进程间内部通信(例如 Broker 在处理查询时与 Historical 通信)所使用的认证方案。关键约束是:所选 Escalator 的认证方案必须被 druid.auth.authenticatorChain 中某个 Authenticator 支持;同理,Authenticator 扩展实现者若希望其方案可用于内部通信,也必须同时提供对应的 Escalator 实现。
Escalator 接口定义在 Escalator.java,并通过 @JsonTypeInfo(..., defaultImpl = NoopEscalator.class) 将 noop 设为默认实现(Escalator.java)。接口核心方法:
HttpClient createEscalatedClient(HttpClient baseClient);
AuthenticationResult createEscalatedAuthenticationResult();
createEscalatedClient:返回一个包装后的 HttpClient,该客户端在请求中自动携带"内部系统用户"的凭据,用于 Druid 节点间通信(如 Broker 与 Historical 之间的查询执行);createEscalatedAuthenticationResult:返回代表"内部系统用户"身份的AuthenticationResult。
说明:原文档中还提及
createEscalatedJettyClient(org.eclipse.jetty.client.HttpClient baseClient)这一针对 Jetty HttpClient 的变体方法。当前仓库server模块的Escalator接口声明了两个核心方法(见上),Jetty 变体由扩展实现根据自身需求提供,这是接口演进与扩展自主性的体现。
Noop Escalator
内置默认的 noop Escalator 专为默认的 AllowAll Authenticator/Authorizer 设计,仅适用于未安全配置。其实现非常直白(NoopEscalator.java):
createEscalatedClient直接返回原始baseClient,不做任何包装(NoopEscalator.java);createEscalatedAuthenticationResult直接返回AllowAllAuthenticator.ALLOW_ALL_RESULT,即身份为allowAll(NoopEscalator.java)。
Authorizer 授权器
授权决策由 Authorizer 负责,druid.auth.authorizers 属性决定启用的 Authorizer 实现。内置实现有两个:default 与 noop,其余由扩展提供。例如启用 druid-basic-security 的 basic 授权器:
druid.auth.authorizers=["basic"]
任意一个请求只会由一个 Authorizer 完成授权(Only a single Authorizer will authorize any given request)。
Authorizer 接口非常精简(Authorizer.java):
Access authorize(AuthenticationResult authenticationResult, Resource resource, Action action);
其语义是:判断 authenticationResult 所代表的身份是否有权对 resource 执行 action,返回不可为空的 Access 对象。每个节点每种 Authorizer 实现只创建一个实例;安全敏感端点会从请求的 Druid-Auth-Token 属性中取出身份字符串,交给 Authorizer 做 authorize() 检查(见 Authorizer.java 的接口注释)。
AllowAll 授权器
类型名为 allowAll 的内置 Authorizer 接受所有请求。其实现只是直接返回 Access.OK(AllowAllAuthorizer.java),与 AllowAll 认证器一样仅用于默认未安全配置。
默认未安全配置(Default Unsecured Configuration)
当上述核心属性缺省时,Druid 会自动落到"全放行"的默认配置,三者必须保持配套:
druid.auth.authenticatorChain为空或未指定时,Druid 创建单个名为allowAll的 AllowAll Authenticator 组成的认证链;druid.auth.authorizers为空或未指定时,Druid 创建单个名为allowAll的 AllowAll Authorizer;druid.escalator.type默认为"noop",与默认的 AllowAll Authenticator/Authorizer 配置匹配。
因此,未做任何安全配置的 Druid 集群,其所有 HTTP 端点对所有人开放,仅适合开发环境或完全可信的内网。
Authenticator 到 Authorizer 的路由
当一个 Authenticator 成功认证请求后,它必须在请求上挂载一个 AuthenticationResult,其中包含两部分关键信息:
- 请求者的身份信息(identity);
- 应执行授权检查的 Authorizer 名称。
Authenticator 实现应通过配置项(如上文 Anonymous/Trusted Domain 认证器中的 authorizerName)让用户选择该 Authenticator 将请求路由到哪个 Authorizer,从而形成"认证方案 + 授权策略"的灵活组合。例如 druid-basic-security 中通常的做法是一个 Basic 认证器配合一个存储了用户权限映射的 Basic 授权器。
内部系统用户(Internal System User)
Druid 进程之间(非用户发起的通信)发起的内部请求也必须携带认证凭据。这类请求应使用一个代表 Druid 集群本身的身份——"内部系统用户",并拥有全部访问权限。具体如何定义该用户由扩展实现决定,但所有参与方必须遵守以下契约:
- Authorizer 侧:授权器实现必须能够识别并授权"内部系统用户"身份,赋予其全部访问权限;
- Authenticator 侧:凡是宣称支持 Druid 内部通信的 Authenticator 实现,必须能够识别对应 Escalator 实现所提供的"内部系统用户"凭据;
- Escalator 侧:负责在内部通信中注入该用户的凭据与身份,通过上文
createEscalatedClient/createEscalatedAuthenticationResult(以及扩展可选的 Jetty 变体)完成。
这套机制保证了:外部用户认证走认证链 + 授权器,内部节点通信则通过 Escalator "提升"为集群自身的特权身份,二者互不干扰。
扩展实现者注意事项:保留配置属性名
对于扩展实现者,官方文档特别提醒以下配置属性被保留用于 Authenticator 与 Authorizer 的名称注入:
druid.auth.authenticator.<authenticator-name>.name=<authenticator-name>
druid.auth.authorizer.<authorizer-name>.name=<authorizer-name>
这些属性以 @JsonProperty 参数形式将认证器/授权器名称传递给实现,在同一类型配置了多个实例时尤为有用——例如两个同为 basic 类型的授权器,可通过 name 区分彼此。从源码看,AnonymousAuthenticator、TrustedDomainAuthenticator 的构造函数确实都以 @JsonProperty("name") String name 接收该值,并把它写入 AuthenticationResult(见 AnonymousAuthenticator.java、TrustedDomainAuthenticator.java)。
相关资源
- 扩展级安全实现:Basic 认证/授权与 LDAP 见 extensions-core/druid-basic-security 及 auth-ldap.md,Kerberos 见 extensions-core/druid-kerberos;
- 认证/授权接口与内置实现源码:Authenticator.java、Authorizer.java、Escalator.java、AuthConfig.java;
- 内置实现:AllowAllAuthenticator.java、AnonymousAuthenticator.java、TrustedDomainAuthenticator.java、AllowAllAuthorizer.java、NoopEscalator.java;
- 行为验证测试:TrustedDomainAuthenticatorTest.java。
实践建议:生产环境不要停留在本文所述的默认 AllowAll 配置上,应至少启用 druid-basic-security(HTTP Basic + 权限映射)并配置 druid.escalator.type 为与之匹配的方案(如 basic 扩展提供的 Escalator),同时用 druid.auth.unsecuredPaths 放行健康检查端点、按需评估 druid.auth.allowUnauthenticatedHttpOptions 的风险后再开启。