You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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_SERVICE
  • scope: 权限范围(比如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. 测试验证

  1. 重启Registry容器:docker-compose down && docker-compose up -d
  2. 执行登录命令:docker login registry.MY-DOMAIN.com,输入邮箱和密码
  3. 尝试推送镜像: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 12:25:28