RabbitMQ SSL认证问题:如何基于客户端公钥实现身份验证
RabbitMQ 基于客户端公钥的无密码身份认证实现
要实现类似ZeroMQ从文件夹加载公钥做身份校验的功能,RabbitMQ可以通过自定义认证后端或结合外部脚本来实现,以下是两种可行方案:
方案一:自定义Erlang认证后端(原生贴合需求)
直接编写Erlang模块实现公钥比对逻辑,无需依赖外部服务:
- 编写认证模块代码
创建rabbitmq_pubkey_auth.erl文件,实现RabbitMQ认证回调:
-module(rabbitmq_pubkey_auth). -behaviour(rabbit_auth_backend). -export([user_login_authentication/2, user_login_authorization/1, check_vhost_access/3, check_resource_access/4, check_topic_access/4]). user_login_authentication(#{ssl_cert := Cert}, _AuthProps) -> % 提取客户端证书中的公钥(DER格式) ClientCert = public_key:pkix_decode_cert(Cert, otp), ClientPubKeyInfo = ClientCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo, ClientPubKeyDer = public_key:der_encode('SubjectPublicKeyInfo', ClientPubKeyInfo), % 加载授权公钥文件夹中的所有公钥 PubKeyDir = "/etc/rabbitmq/authorized_pubkeys", {ok, Files} = file:list_dir(PubKeyDir), % 遍历比对公钥 case lists:any(fun(File) -> FilePath = filename:join(PubKeyDir, File), {ok, PubKeyData} = file:read_file(FilePath), [{PubKeyDer, _}] = public_key:pem_decode(PubKeyData), PubKeyDer =:= ClientPubKeyDer end, Files) of true -> {ok, #{user => <<"authorized_client">>, tags => [administrator]}}; % 可根据公钥配置不同权限标签 false -> {refused, "Unauthorized public key", []} end; user_login_authentication(_Creds, _AuthProps) -> {refused, "Only SSL certificate authentication is allowed", []}. % 默认实现其他授权回调(可按需修改) user_login_authorization(_User) -> ok. check_vhost_access(_User, _VHost, _AuthzContext) -> ok. check_resource_access(_User, _VHost, _Resource, _Permission) -> ok. check_topic_access(_User, _VHost, _Resource, _Permission) -> ok.
- 编译并部署模块
- 用Erlang编译器编译成
.beam文件:erlc rabbitmq_pubkey_auth.erl - 将编译后的文件复制到RabbitMQ Docker容器的Erlang代码目录(例如
/usr/lib/rabbitmq/lib/rabbitmq_server-<version>/ebin)
- 修改RabbitMQ配置
在rabbitmq.conf中启用自定义认证后端并配置SSL基础参数:
# SSL监听配置 listeners.ssl.default = 5671 ssl_options.cacertfile = /etc/rabbitmq/ca.pem ssl_options.certfile = /etc/rabbitmq/server_cert.pem ssl_options.keyfile = /etc/rabbitmq/server_key.pem ssl_options.verify = verify_peer ssl_options.fail_if_no_peer_cert = true # 启用自定义认证后端 auth_backends = [rabbitmq_pubkey_auth, rabbit_auth_backend_internal]
- 挂载授权公钥文件夹
将存放授权客户端公钥的文件夹(PEM格式)挂载到容器的/etc/rabbitmq/authorized_pubkeys路径,确保RabbitMQ用户(uid 999)有读取权限。
方案二:结合外部脚本(无需Erlang开发)
通过RabbitMQ的rabbitmq-auth-backend-http插件,调用外部HTTP服务完成公钥比对:
启用HTTP认证插件
在容器内执行命令启用插件:rabbitmq-plugins enable rabbitmq_auth_backend_http配置RabbitMQ HTTP认证
修改rabbitmq.conf:
# SSL基础配置同方案一 listeners.ssl.default = 5671 ssl_options.cacertfile = /etc/rabbitmq/ca.pem ssl_options.certfile = /etc/rabbitmq/server_cert.pem ssl_options.keyfile = /etc/rabbitmq/server_key.pem ssl_options.verify = verify_peer ssl_options.fail_if_no_peer_cert = true # HTTP认证配置 auth_backends = [rabbit_auth_backend_http, rabbit_auth_backend_internal] auth_http.url = http://localhost:8080/auth auth_http.method = post auth_http.user_path = /login
- 编写HTTP认证服务(示例用Python Flask)
创建pubkey_auth_server.py:
from flask import Flask, request import os from cryptography import x509 from cryptography.hazmat.primitives import serialization app = Flask(__name__) AUTH_PUBKEY_DIR = "/etc/rabbitmq/authorized_pubkeys" def load_authorized_pubkeys(): """加载授权文件夹中的所有公钥(DER格式)""" pubkey_der_list = [] for filename in os.listdir(AUTH_PUBKEY_DIR): if not filename.endswith(".pem"): continue with open(os.path.join(AUTH_PUBKEY_DIR, filename), "rb") as f: pubkey = serialization.load_pem_public_key(f.read()) pubkey_der = pubkey.public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) pubkey_der_list.append(pubkey_der) return pubkey_der_list @app.route('/login', methods=['POST']) def authenticate(): # 从请求头获取客户端证书 client_cert_pem = request.headers.get('X-SSL-Client-Cert') if not client_cert_pem: return {"refused": "No client certificate provided"}, 401 # 解析客户端证书并提取公钥 cert = x509.load_pem_x509_certificate(client_cert_pem.encode('utf-8')) client_pubkey_der = cert.public_key().public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) # 比对公钥 if client_pubkey_der in load_authorized_pubkeys(): return {"user": "authorized_client", "tags": ["administrator"]} else: return {"refused": "Unauthorized public key"}, 401 if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)
- 部署HTTP服务
将脚本和授权公钥文件夹挂载到Docker容器中,确保服务和RabbitMQ通信正常。
通用注意事项
- 授权公钥需为PEM格式,可从客户端证书中提取:
openssl x509 -in client_cert.pem -pubkey -noout > client_pubkey.pem - Docker容器内的SSL文件和公钥文件夹需设置正确权限:
chown -R 999:999 /path/to/mounted/files - 测试连接可使用命令:
openssl s_client -connect <rabbitmq-host>:5671 -cert client_cert.pem -key client_key.pem
内容的提问来源于stack exchange,提问作者nogabemist
相关产品推荐
相关产品推荐

