本文档详细阐述 Judge0 的双重安全架构:认证(Authentication) 与 授权(Authorization)。两者协同工作,形成分层的安全防护体系,确保 API 的安全访问与特权操作的严格管控。
核心架构概述
Judge0 采用令牌(Token)-based 的安全模型,通过 SessionsController 作为所有控制器的基类实现安全过滤链。
flowchart TD
subgraph 请求入口
A[API Request] --> B[IP 地址验证]
end
subgraph 认证层 AUTHN
B --> C{有效 AUTHN_TOKEN?}
C -->|是| D[认证通过]
C -->|否| E[401 Unauthorized]
end
subgraph 授权层 AUTHZ
D --> F{需要授权端点?}
F -->|是| G{有效 AUTHZ_TOKEN?}
F -->|否| H[普通请求处理]
G -->|是| H
G -->|否| I[403 Forbidden]
end
subgraph 端点处理
H --> J[执行对应操作]
end
架构特点:认证层确保所有请求的有效性,授权层在认证基础上进一步筛选特权操作。
Sources: sessions_controller.rb
认证与授权机制对比
| 特性维度 | 认证(Authentication) | 授权(Authorization) |
|---|---|---|
| 环境变量前缀 | AUTHN_* |
AUTHZ_* |
| 默认请求头 | X-Auth-Token |
X-Auth-User |
| 验证目的 | 验证请求者身份 | 验证操作权限 |
| 保护范围 | 所有 API 端点 | 特权操作端点 |
| 配置项 | AUTHN_HEADER, AUTHN_TOKEN |
AUTHZ_HEADER, AUTHZ_TOKEN |
| 默认状态 | 可选(空 Token 禁用) | 可选(空 Token 禁用) |
| 失败响应 | 401 Unauthorized |
403 Forbidden |
Sources: judge0.conf
控制器层级与继承关系
classDiagram
class ActionController::API {
<<基类>>
}
class SessionsController {
+verify_ip_address
+authenticate_request
+authorize_request
+safe_compare
}
class ApplicationController {
+pagination_dict
}
class SubmissionsController {
+index [需AUTHZ]
+destroy [需AUTHZ]
+show
+create
+batch_create
}
ActionController::API <|-- SessionsController
SessionsController <|-- ApplicationController
ApplicationController <|-- SubmissionsController
设计模式:所有控制器继承自 SessionsController,自动获得安全过滤能力。SubmissionsController 仅对特定方法添加 authorize_request 过滤。
Sources: sessions_controller.rb Sources: application_controller.rb Sources: submissions_controller.rb
IP 地址过滤
verify_ip_address 方法实现 IP 级别的访问控制,支持白名单与黑名单机制。
def verify_ip_address
@@disallowed_ip_addresses ||= ENV['DISALLOW_IP'].to_s.split - ENV['ALLOW_IP'].to_s.split
@@allowed_ip_addresses ||= ENV['ALLOW_IP'].to_s.split - ENV['DISALLOW_IP'].to_s.split
head :forbidden if @@disallowed_ip_addresses.include?(request.remote_ip)
head :forbidden if @@allowed_ip_addresses.present? && !@@allowed_ip_addresses.include?(request.remote_ip)
end
逻辑说明:
- 黑名单(
DISALLOW_IP):明确禁止的 IP 地址列表 - 白名单(
ALLOW_IP):明确允许的 IP 地址列表 - 优先级:白名单为空时允许所有;白名单非空时仅允许白名单中的 IP
flowchart LR
A[请求IP] --> B{DISALLOW_IP<br/>包含?}
B -->|是| C[403 Forbidden]
B -->|否| D{ALLOW_IP<br/>非空?}
D -->|是| E{ALLOW_IP<br/>包含?}
E -->|否| C
E -->|是| F[通过验证]
D -->|否| F
Sources: sessions_controller.rb
认证请求流程
authenticate_request 方法使用安全的时序比较算法验证认证令牌。
def authenticate_request
head :unauthorized if safe_compare(Rails.application.secrets.authn_token, Rails.application.secrets.authn_header)
end
def safe_compare(token, header)
token = token.to_s
header = header.to_s
return false unless token.present?
provided_token = (request.headers[header] || params[header]).to_s
token.split.each do |value|
return false if ActiveSupport::SecurityUtils.secure_compare(value, provided_token)
end
true
end
安全特性:
- 时序安全比较:使用
ActiveSupport::SecurityUtils.secure_compare防止时序攻击 - 多令牌支持:Token 字符串按空格分割,支持配置多个有效令牌
- 灵活来源:令牌可来自 HTTP Header 或 URL 参数
响应逻辑:当 safe_compare 返回 true 时表示令牌不匹配,返回 false 时表示认证通过。
Sources: sessions_controller.rb
授权请求流程
authorize_request 方法仅对标记为 only: [:authorize] 的端点生效。
def authorize_request
head :forbidden unless Rails.application.secrets.authz_token.present?
head :forbidden if safe_compare(Rails.application.secrets.authz_token, Rails.application.secrets.authz_header)
end
执行条件:
- 必须先通过认证(
authenticate_request) - 端点必须显式声明
before_action :authorize_request
Sources: sessions_controller.rb
特权端点保护矩阵
| 端点 | 路径 | 认证 | 授权 | 说明 |
|---|---|---|---|---|
GET /submissions |
index | ✓ | ✓ | 分页查询所有提交 |
GET /submissions/:token |
show | ✓ | – | 查询单个提交 |
POST /submissions |
create | ✓ | – | 创建新提交 |
DELETE /submissions/:token |
destroy | ✓ | ✓ | 删除提交(需配置 ENABLE_SUBMISSION_DELETE=true) |
POST /submissions/batch |
batch_create | ✓ | – | 批量创建 |
GET /submissions/batch |
batch_show | ✓ | – | 批量查询 |
POST /authenticate |
authenticate | – | – | 认证端点 |
POST /authorize |
authorize | ✓ | ✓ | 授权验证端点 |
Sources: routes.rb Sources: submissions_controller.rb
配置指南
1. 启用认证
# 在 judge0.conf 中配置
AUTHN_HEADER=X-Auth-Token
AUTHN_TOKEN=yourSecretToken123
客户端调用示例:
# 通过 Header 传递
curl -H "X-Auth-Token: yourSecretToken123" \
http://localhost:2358/submissions
# 通过参数传递
curl http://localhost:2358/submissions?X-Auth-Token=yourSecretToken123
2. 启用授权
# 在 judge0.conf 中配置
AUTHZ_HEADER=X-Auth-User
AUTHZ_TOKEN=adminSecretToken456
3. 多令牌配置
支持空格分隔的多个令牌:
AUTHN_TOKEN=token1 token2 token3
AUTHZ_TOKEN=admin1 admin2
4. IP 访问控制
# 仅允许指定 IP
ALLOW_IP="192.168.1.100 10.0.0.50"
# 禁止指定 IP
DISALLOW_IP="1.2.3.4 5.6.7.8"
5. 完整配置示例
# judge0.conf
################################################################################
# Judge0 Server Access Configuration
################################################################################
ALLOW_IP="192.168.10.10 96.239.226.228 208.23.207.242"
DISALLOW_IP="10.0.0.1"
################################################################################
# Judge0 Authentication Configuration
################################################################################
AUTHN_HEADER=X-Judge0-Token
AUTHN_TOKEN=mySecretAuthToken
################################################################################
# Judge0 Authorization Configuration
################################################################################
AUTHZ_HEADER=X-Judge0-User
AUTHZ_TOKEN=myAdminAuthToken
Sources: judge0.conf Sources: scripts/load-config
安全机制详解
时序安全比较
ActiveSupport::SecurityUtils.secure_compare(value, provided_token)
防护目的:防止攻击者通过测量响应时间差异推断令牌内容。
缓存机制
@@disallowed_ip_addresses ||= ENV['DISALLOW_IP'].to_s.split - ENV['ALLOW_IP'].to_s.split
性能优化:使用实例变量缓存解析结果,避免每次请求重复解析环境变量。
维护模式集成
def check_maintenance
@@maintenance_message ||= ENV['MAINTENANCE_MESSAGE']
if Config::MAINTENANCE_MODE
render json: { error: @@maintenance_message }, status: :service_unavailable
end
end
维护模式下,create 和 destroy 操作将被阻止,与授权机制形成双重保护。
Sources: sessions_controller.rb Sources: submissions_controller.rb
CORS 跨域配置
Judge0 通过 Rack::Cors 中间件支持跨域请求配置。
config.middleware.insert_before 0, Rack::Cors do
origins = []
disallowed_origins = (ENV['DISALLOW_ORIGIN'].to_s.split - ENV['ALLOW_ORIGIN'].to_s.split).collect{ |s| s.gsub(".", "\\.") }.join("|")
if disallowed_origins.present?
origins.append(Regexp.new("^(?:(?!#{disallowed_origins}).)*$"))
end
origins += (ENV['ALLOW_ORIGIN'].presence || (origins.present? ? '' : '*')).split
allow do
origins origins
resource '*', headers: :any, methods: :any
end
end
配置选项:
ALLOW_ORIGIN:允许的源(多个空格分隔)DISALLOW_ORIGIN:禁止的源(多个空格分隔)
Sources: application.rb
最佳实践建议
- 分层安全部署:外层使用 Nginx/负载均衡器实现 IP 限制,内部依赖 Judge0 认证机制
- 令牌轮换:定期更新
AUTHN_TOKEN和AUTHZ_TOKEN,记录变更历史 - 最小权限:仅对需要删除权限的部署启用
AUTHZ_TOKEN - 监控日志:生产环境建议配置日志记录,监控
401和403响应 - 令牌强度:使用足够长度(建议 32+ 字符)的随机字符串作为令牌
下一步学习
完成授权机制的学习后,建议继续阅读:
- 认证机制 – 深入了解认证流程与实现细节
- 创建提交 API – 了解提交创建的安全要求
- 系统配置信息 API – 了解 config_info 端点的访问控制
相关条目
- 14-认证机制
- 18-安全机制与沙箱隔离