令牌认证实施

预计阅读时间:8分钟

Docker Registry v2 Bearer令牌规范

该规范涵盖了docker/distributionv2注册中心的身份验证架构的实现。具体来说,它描述了docker/distribution已采用的JSON Web令牌架构,以实现由身份验证服务发布并由注册表理解的客户端不透明Bearer令牌。

该文档大量借鉴了JSON Web令牌草案规范

获取承载令牌

对于此示例,客户端向以下URL发出HTTP GET请求:

https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba/my-app:pull,push

令牌服务器应该首先尝试使用随请求提供的任何身份验证凭据对客户端进行身份验证。从Docker 1.8开始,Docker Engine中的注册表客户端仅支持对这些令牌服务器的基本身份验证。如果尝试对令牌服务器进行身份验证失败,则令牌服务器应返回一个401 Unauthorized响应,指示提供的凭据无效。

令牌服务器是否需要身份验证取决于该访问控制提供者的策略。有些请求可能需要身份验证才能确定访问权限(例如,推送或拉出私有存储库),而其他请求则可能不需要身份验证(例如,从公共存储库中拉出)。

在对客户端进行身份验证之后(如果未尝试进行身份验证,则可能只是匿名客户端),令牌服务器必须接下来查询其访问控制列表,以确定该客户端是否具有所请求的范围。在此示例请求中,如果我已通过身份验证为用户jlhawn,则令牌服务器将确定我samalba/my-app 对实体托管的存储库具有什么访问权限registry.docker.io

令牌服务器确定了客户端对scope参数中请求的资源的访问权限后,它将采用对每个资源的请求操作集与实际上已被授予客户端的操作集的交集。如果客户端仅具有请求的访问权限的子集,则不得将其视为错误,因为令牌授权服务器不负责在此工作流中指示授权错误。

继续执行示例请求,令牌服务器将发现客户端对存储库的授予访问权限集是[pull, push]与请求的访问权限相交时[pull, push]产生的一组相等的访问权限。如果仅找到授予的访问集,[pull]则相交的集将仅为[pull]。如果客户端无权访问存储库,则相交的集合将为空[]

正是这个相交的访问集被放置在返回的令牌中。

服务器现在将构造一个JSON Web令牌以进行签名和返回。JSON Web令牌包含3个主要部分:

  1. 标头

    JSON Web令牌的标头是标准的JOSE标头。“ typ”字段将为“ JWT”,并且还将包含“ alg”,该“ alg”标识用于产生签名的签名算法。它还必须有一个“ kid”字段,代表用于对令牌进行签名的密钥的ID。

    “孩子”字段必须采用libtrust指纹兼容格式。可以通过以下步骤生成这种格式:

    1. 使用签署了JWT令牌的DER编码的公共密钥。

    2. 从中创建一个SHA256哈希,并将其截断为240位。

    3. 将结果分成12个:以定界符为基数的base32编码的组。

    这是一个JSON Web令牌的JOSE标头示例(为便于阅读,使用空格格式化):

    {
        "typ": "JWT",
        "alg": "ES256",
        "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"
    }
    

    它指定此对象将是使用带有给定ID的密钥使用椭圆曲线签名算法(使用SHA256哈希)使用具有给定ID的密钥签名的JSON Web令牌。

  2. 索赔集

    Claim Set是一个JSON结构,其中包含以下标准注册的声明名称字段:

    iss (发行人)
    令牌的发行者,通常是授权服务器的fqdn。
    sub (主题)
    令牌的主题;请求它的客户端的名称或ID。如果客户端未通过身份验证,则该字段应为空(`“”`)。
    aud (观众)
    令牌的目标受众;验证令牌以授权客户端/主题的服务的名称或ID。
    exp (到期)
    该令牌仅应视为在此指定的日期和时间之前有效。
    nbf (不早于)
    在指定的日期和时间之前,令牌不应被视为有效。
    iat (发出于)
    指定授权服务器生成此令牌的日期和时间。
    jti (JWT ID)
    此令牌的唯一标识符。预期的受众可以使用它来防止令牌的重播。

    声明集还将包含此授权服务器规范所独有的私有声明名称:

    access
    具有以下字段的访问条目对象的数组:
    type
    服务托管的资源类型。
    name
    服务托管的给定类型的资源的名称。
    actions
    字符串数组,用于给出对此资源授权的操作。

    这是一个这样的JWT声明集的示例(为了便于阅读,将其格式化为空格):

    {
        "iss": "auth.docker.com",
        "sub": "jlhawn",
        "aud": "registry.docker.com",
        "exp": 1415387315,
        "nbf": 1415387015,
        "iat": 1415387015,
        "jti": "tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws",
        "access": [
            {
                "type": "repository",
                "name": "samalba/my-app",
                "actions": [
                    "pull",
                    "push"
                ]
            }
        ]
    }
    
  3. 签名

    授权服务器将生成一个JOSE标头和没有多余空格的Claim Set,即,上面的JOSE标头将为

    {"typ":"JWT","alg":"ES256","kid":"PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"}
    

    并且上方的“索赔集”为

    {"iss":"auth.docker.com","sub":"jlhawn","aud":"registry.docker.com","exp":1415387315,"nbf":1415387015,"iat":1415387015,"jti":"tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws","access":[{"type":"repository","name":"samalba/my-app","actions":["push","pull"]}]}
    

    然后将此JOSE标头和Claim Set的utf-8表示形式进行url安全的base64编码(无尾随'='缓冲区),从而产生:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0
    

    用于JOSE标头和

    eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0
    

    索赔集。这两个用'。'连接。字符,产生字符串:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0
    

    然后将其用作ES256JOSE标头中指定并在JSON Web算法(JWA)规范草案的第3.4节中完全指定的签名算法的有效负载。

    此示例签名将对服务器使用以下ECDSA密钥:

    {
        "kty": "EC",
        "crv": "P-256",
        "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6",
        "d": "R7OnbfMaD5J2jl7GeE8ESo7CnHSBm_1N2k9IXYFrKJA",
        "x": "m7zUpx3b-zmVE5cymSs64POG9QcyEpJaYCD82-549_Q",
        "y": "dU3biz8sZ_8GPB-odm8Wxz3lNDr1xcAQQPQaOcr1fmc"
    }
    

    使用此密钥的上述有效负载的最终签名为:

    QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
    

    将所有这些与一个.字符连接在一起将得到最终的JWT:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
    

现在可以将其放置在HTTP响应中,并返回给客户端以用于对受众服务进行身份验证:

HTTP/1.1 200 OK
Content-Type: application/json

{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w"}

使用签名的令牌

客户端获得令牌后,它将使用令牌放在HTTPAuthorization标头中的方式再次尝试注册请求,如下所示:

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJWM0Q6MkFWWjpVQjVaOktJQVA6SU5QTDo1RU42Ok40SjQ6Nk1XTzpEUktFOkJWUUs6M0ZKTDpQT1RMIn0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJCQ0NZOk9VNlo6UUVKNTpXTjJDOjJBVkM6WTdZRDpBM0xZOjQ1VVc6NE9HRDpLQUxMOkNOSjU6NUlVTCIsImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5jb20iLCJleHAiOjE0MTUzODczMTUsIm5iZiI6MTQxNTM4NzAxNSwiaWF0IjoxNDE1Mzg3MDE1LCJqdGkiOiJ0WUpDTzFjNmNueXk3a0FuMGM3cktQZ2JWMUgxYkZ3cyIsInNjb3BlIjoiamxoYXduOnJlcG9zaXRvcnk6c2FtYWxiYS9teS1hcHA6cHVzaCxwdWxsIGpsaGF3bjpuYW1lc3BhY2U6c2FtYWxiYTpwdWxsIn0.Y3zZSwaZPqy4y9oRBVRImZyv3m_S9XDHF1tWwN7mL52C_IiA73SJkWVNsvNqpJIn5h7A2F8biv_S2ppQ1lgkbw

RFC 6750的第2.1节:OAuth 2.0授权框架:承载令牌使用也对此进行了描述。

验证令牌

注册表现在必须通过检查其中的声明集来验证用户提供的令牌。注册表将:

  • 确保颁发者(iss索赔)是它信任的授权机构。
  • 确保注册表标识为受众(aud声明)。
  • 检查当前时间是否在nbfexp索赔时间之间。
  • 如果强制使用一次性令牌,请检查jti之前是否未看到JWT ID(声明)值。
    • 为了强制执行此操作,注册表可能会保留jtis的记录,直到exp令牌出现为止,以防止令牌重播。
  • 检查access索赔值,并使用已标识的资源和授权的操作列表,以确定令牌是否授予客户端尝试执行的操作所需的访问级别。
  • 验证令牌的签名有效。

如果不满足这些要求中的任何一个,则注册表将返回 403 Forbidden响应以指示该令牌无效。

注意:仅在工作流中,此时才可能发生授权错误。令牌服务器应该不是当用户没有授权请求返回错误。取而代之的是,返回的令牌应指示客户端确实具有的任何请求范围(请求和授予的访问权限的交集)。如果令牌未提供适当的授权,则注册表将返回相应的错误。

在此过程中的任何时候,注册表都无需回调到授权服务器。仅需要向注册表提供受信任的公钥来验证令牌签名。

注册表本地图像标签存储库分发JWT认证高级