Docker私有仓库令牌认证400错误及邮箱替代用户名登录诉求
解决方案
一、核心结论:可以用邮箱替代用户名登录
Docker login的Username字段只是用户标识符,服务端完全可以将其当作邮箱处理,无需修改Docker客户端行为,只需在API中把接收的username参数作为邮箱去MongoDB查询即可。
二、解决400错误及实现完整令牌认证
1. 修正Docker Registry配置错误
当前配置有三个关键问题,直接导致登录失败:
REGISTRY_AUTH_TOKEN_ISSUER设为"Let's Encrypt":Let's Encrypt是HTTPS证书颁发机构,不是JWT令牌的发行方,必须改为自定义名称(比如"MY-DOMAIN Registry")REGISTRY_AUTH_TOKEN_SERVICE与API端服务名称不匹配:统一设为仓库域名"registry.MY-DOMAIN.com",避免令牌验证失败REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE指向Let's Encrypt证书:该配置用于验证JWT签名的公钥,不是HTTPS证书,需替换为自定义RSA公钥
修正后的docker-compose.yml环境变量及挂载部分:
environment: REGISTRY_HTTP_SECRET: b8f62d22-9a3f-4c73-bf5e-e0864b400bc8 # S3存储配置保留不变 REGISTRY_STORAGE: s3 REGISTRY_STORAGE_S3_ACCESSKEY: XXXXXXXXX REGISTRY_STORAGE_S3_SECRETKEY: XXXXXXXXX REGISTRY_STORAGE_S3_REGION: us-east-1 REGISTRY_STORAGE_S3_BUCKET: docker-registry # 修正令牌认证配置 REGISTRY_AUTH: token REGISTRY_AUTH_TOKEN_REALM: "https://api.MY-DOMAIN.com/api/developer-auth/login" REGISTRY_AUTH_TOKEN_SERVICE: "registry.MY-DOMAIN.com" REGISTRY_AUTH_TOKEN_ISSUER: "MY-DOMAIN Registry" REGISTRY_AUTH_TOKEN_AUTOREDIRECT: false # HTTPS证书保留Let's Encrypt配置 REGISTRY_HTTP_TLS_CERTIFICATE: "/certs/live/registry.MY-DOMAIN.com/fullchain.pem" REGISTRY_HTTP_TLS_KEY: "/certs/live/registry.MY-DOMAIN.com/privkey.pem" # 挂载自定义RSA公钥用于验证令牌 REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE: "/certs/token-public.pem" volumes: - "/etc/letsencrypt:/certs" - "/path/to/your/rsa-public.pem:/certs/token-public.pem" # 新增挂载自己的公钥
2. 生成RSA密钥对
生成用于签名JWT的RSA密钥对:
# 生成私钥 openssl genrsa -out token-private.pem 2048 # 生成公钥 openssl rsa -in token-private.pem -pubout -out token-public.pem
将token-public.pem挂载到Registry容器中(对应上面的volume配置),私钥保存在Express API服务器上用于签名令牌。
3. 实现符合Docker规范的Express令牌服务
Docker令牌认证遵循OAuth 2.0 JWT标准,用户执行docker login时,Docker会向你的realm端点发送GET请求,携带以下参数:
service: 对应Registry配置的REGISTRY_AUTH_TOKEN_SERVICEscope: 权限范围(比如repository:*:pull,push)account: 用户输入的"用户名"(即邮箱)
同时,Docker会将邮箱和密码通过Basic Auth头传递(格式为Basic base64(邮箱:密码))。
以下是Express API核心实现代码:
const express = require('express'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); const fs = require('fs'); const User = require('./models/User'); // 你的MongoDB用户模型 const router = express.Router(); // 加载RSA私钥 const privateKey = fs.readFileSync('/path/to/token-private.pem', 'utf8'); // 解析Basic Auth头的工具函数 const parseBasicAuth = (authHeader) => { if (!authHeader || !authHeader.startsWith('Basic ')) return null; const decoded = Buffer.from(authHeader.slice(6), 'base64').toString('utf8'); const [email, password] = decoded.split(':'); return { email, password }; }; router.get('/api/developer-auth/login', async (req, res) => { // 解析Basic Auth中的邮箱和密码 const authData = parseBasicAuth(req.headers.authorization); if (!authData) return res.status(401).send('Missing credentials'); const { email, password } = authData; const { service, scope } = req.query; // 验证用户(根据你的MongoDB模型调整) const user = await User.findOne({ email }); if (!user || !(await user.comparePassword(password))) { // 假设模型有密码验证方法 return res.status(401).send('Invalid email or password'); } // 生成符合Docker要求的JWT令牌 const token = jwt.sign( { iss: 'MY-DOMAIN Registry', // 必须与Registry的issuer配置一致 aud: service, // 必须与Registry的service配置一致 sub: email, exp: Math.floor(Date.now() / 1000) + 3600, // 令牌有效期1小时 nbf: Math.floor(Date.now() / 1000), iat: Math.floor(Date.now() / 1000), jti: crypto.randomUUID(), // 配置用户权限,支持指定镜像的拉取/推送权限 access: scope ? [ { type: 'repository', name: scope.split(':')[1], actions: scope.split(':')[2].split(',') } ] : [] }, privateKey, { algorithm: 'RS256' } // 必须用RSA256算法 ); res.json({ token }); }); module.exports = router;
4. 测试验证
- 重启Registry容器:
docker-compose down && docker-compose up -d - 执行登录命令:
docker login registry.MY-DOMAIN.com,输入邮箱和密码 - 尝试推送镜像:
docker tag local-image:tag registry.MY-DOMAIN.com/local-image:tag && docker push registry.MY-DOMAIN.com/local-image:tag
三、常见问题排查
- 仍返回400错误:检查Express API是否正确处理GET请求,是否解析了
service、scope参数,Basic Auth头是否解析正确 - 返回401错误:检查JWT的issuer、audience是否与Registry配置一致,公钥是否正确挂载,用户密码验证是否通过
- 镜像推送失败:检查JWT的
access字段是否包含对应的权限(比如push)
内容的提问来源于stack exchange,提问作者Channaveer Hakari
相关产品推荐
相关产品推荐

