DolphinScheduler作为一款开源的分布式工作流任务调度系统,在企业级数据管道管理中扮演着重要角色。随着多云环境和混合IT架构的普及,传统的账号密码认证方式已经无法满足现代企业的安全需求。这个由Google Summer of Code(GSoC)支持的开发项目,通过引入OIDC(OpenID Connect)认证协议,为平台带来了符合云原生时代标准的安全认证方案。
OIDC是基于OAuth 2.0协议的身份层,它允许客户端通过授权服务器的认证流程来验证终端用户身份。与传统的SAML协议相比,OIDC采用更轻量级的JSON Web Token(JWT)格式,特别适合RESTful API场景。印度开发者团队选择这一方案,主要考虑到三点技术优势:
项目采用标准的OIDC Provider/Client模式,在DolphinScheduler服务端新增了oidc模块作为认证中间件。关键组件包括:
集成流程采用Authorization Code Flow with PKCE(Proof Key for Code Exchange),这是目前最安全的OIDC授权模式。具体步骤包括:
认证模块的核心逻辑集中在OIDCAuthenticator类中,主要处理以下功能:
java复制public class OIDCAuthenticator implements Authenticator {
// 初始化OIDC配置
private OIDCConfig loadConfig() {
return new OIDCConfig()
.setClientId(conf.getString("security.oidc.client-id"))
.setIssuerURI(conf.getString("security.oidc.issuer-uri"))
.setScopes(conf.getString("security.oidc.scopes", "openid profile email"));
}
// 认证主流程
public AuthenticationResult authenticate(HttpServletRequest request) {
String code = request.getParameter("code");
if (code == null) {
return initiateAuthFlow(request);
}
return processCallback(request, code);
}
}
Token验证环节使用Nimbus JOSE+JWT库处理JWT:
java复制JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
JWTClaimsSet claims = SignedJWT.parse(idToken).verify(verifier).getJWTClaimsSet();
// 验证标准声明
if (!claims.getIssuer().equals(oidcConfig.getIssuer())) {
throw new AuthenticationException("Invalid token issuer");
}
if (claims.getExpirationTime().before(new Date())) {
throw new AuthenticationException("Token expired");
}
为兼容DolphinScheduler原有的RBAC系统,项目实现了灵活的声明映射机制。在配置文件中可以指定如何从OIDC声明中提取用户属性:
properties复制security.oidc.claim-mapping.username=preferred_username
security.oidc.claim-mapping.email=email
security.oidc.claim-mapping.tenant=tenant_id
对于需要自定义属性转换的场景,可以通过实现ClaimTransformer接口注入处理逻辑:
java复制public interface ClaimTransformer {
Map<String, Object> transform(Map<String, Object> claims);
}
在common.properties中新增以下OIDC配置项:
properties复制# 启用OIDC认证
security.authentication.type=OIDC
# OIDC提供商配置
security.oidc.issuer-uri=https://keycloak.example.com/auth/realms/master
security.oidc.client-id=dolphinscheduler
security.oidc.client-secret=your-client-secret
security.oidc.scopes=openid profile email
# 回调URL必须与IdP中注册的一致
security.oidc.redirect-uri=https://ds.example.com/redirect_uri
Keycloak配置步骤:
Azure AD配置要点:
https://{ds-hostname}/api/oidc/callback对于需要支持多租户的场景,可以通过以下两种方式实现:
properties复制# 为不同租户配置不同的OIDC Client
security.oidc.tenants.tenant1.issuer-uri=https://idp.example.com/tenant1
security.oidc.tenants.tenant1.client-id=ds-tenant1
security.oidc.tenants.tenant2.issuer-uri=https://idp.example.com/tenant2
security.oidc.tenants.tenant2.client-id=ds-tenant2
request_transformer从HTTP请求头或路径参数中提取租户信息:java复制public class TenantAwareRequestTransformer implements RequestTransformer {
public HttpServletRequest transform(HttpServletRequest request) {
String tenant = request.getHeader("X-Tenant-ID");
// 根据租户信息动态设置OIDC配置
OIDCConfig config = tenantConfigs.get(tenant);
request.setAttribute(OIDC_CONFIG_ATTRIBUTE, config);
return request;
}
}
项目实现了完整的Token验证链,包括:
重要安全实践:Access Token不直接用于身份认证,仅用于访问UserInfo端点。所有身份决策基于经过完整验证的ID Token。
OIDC集成后,会话安全通过以下机制保障:
会话超时配置建议:
properties复制# 会话有效期应小于等于ID Token的exp时间
server.servlet.session.timeout=4h
# 定期检查后端会话状态
security.session.validation-interval=15m
所有认证事件记录详细审计日志,包含关键字段:
json复制{
"timestamp": "2023-08-20T10:00:00Z",
"eventType": "OIDC_AUTH",
"userId": "johndoe",
"clientIp": "192.168.1.100",
"oidcClaims": {
"iss": "https://idp.example.com",
"sub": "1234567890",
"auth_time": 1692525600
}
}
为避免频繁访问JWKS端点,实现了两级缓存:
缓存配置示例:
java复制LoadingCache<String, PublicKey> jwkCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(issuer -> fetchPublicKey(issuer));
在高并发场景下,采用以下优化措施:
java复制// 使用Semaphore控制并发量
Semaphore semaphore = new Semaphore(50);
public UserInfo getUserInfo(String accessToken) {
if (!semaphore.tryAcquire()) {
throw new RateLimitExceededException();
}
try {
return userInfoClient.call(accessToken);
} finally {
semaphore.release();
}
}
为支持渐进式迁移,系统可以同时启用多种认证方式:
properties复制security.authentication.type=OIDC,PASSWORD
在代码层面通过AuthenticationFilter链实现:
java复制List<Authenticator> authenticators = new ArrayList<>();
if (config.hasOIDC()) {
authenticators.add(new OIDCAuthenticator());
}
if (config.hasPassword()) {
authenticators.add(new PasswordAuthenticator());
}
首次OIDC登录时自动创建本地用户账户,同步策略包括:
sql复制-- 用户表新增OIDC关联字段
ALTER TABLE t_ds_user
ADD COLUMN oidc_issuer VARCHAR(255),
ADD COLUMN oidc_subject VARCHAR(255),
ADD UNIQUE INDEX idx_oidc (oidc_issuer, oidc_subject);
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 认证后跳转空白页 | 回调URL未正确配置 | 检查IdP和DS中的redirect_uri是否完全匹配 |
| Invalid token signature | JWK缓存过期或时钟不同步 | 强制刷新JWK缓存,检查系统时间同步 |
| User not found | 声明映射配置错误 | 检查security.oidc.claim-mapping配置 |
| 频繁要求重新登录 | 会话超时设置过短 | 调整server.servlet.session.timeout |
启用DEBUG日志获取详细认证流程信息:
properties复制logging.level.org.apache.dolphinscheduler.api.security.oidc=DEBUG
关键日志事件包括:
实现OIDCClaimProcessor接口可扩展声明处理逻辑:
java复制public interface OIDCClaimProcessor {
void processClaims(Map<String, Object> claims, User user);
}
// 示例:从自定义声明中设置用户部门
public class DepartmentClaimProcessor implements OIDCClaimProcessor {
public void processClaims(Map<String, Object> claims, User user) {
user.setDepartment((String) claims.get("department"));
}
}
通过AuthenticationEventListener订阅认证事件:
java复制public interface AuthenticationEventListener {
void onAuthenticationSuccess(AuthenticationEvent event);
void onAuthenticationFailure(AuthenticationEvent event);
}
// 示例:记录认证成功到审计系统
public class AuditLogListener implements AuthenticationEventListener {
public void onAuthenticationSuccess(AuthenticationEvent event) {
auditService.log(event.getUser(), "OIDC_LOGIN");
}
}
在4核8G的测试环境中,不同并发下的认证性能表现:
| 并发用户数 | 平均响应时间 | 吞吐量 (req/s) | 错误率 |
|---|---|---|---|
| 50 | 120ms | 420 | 0% |
| 100 | 180ms | 550 | 0% |
| 200 | 250ms | 720 | 0.2% |
| 500 | 420ms | 950 | 1.5% |
优化建议:
项目团队规划了以下增强功能:
社区贡献者可以通过以下方式参与: