OpenID连接

OpenID连接支持

django-oauth-toolkit 支持OpenID Connect(OIDC),可标准化身份验证流并提供与其他系统的即插即用集成。OIDC构建在OAuth 2.0之上,可提供:

  • 生成ID令牌作为登录过程的一部分。这些是描述用户的JWT,可用于向您的应用程序验证他们的身份。

  • 提供程序基于元数据的自动配置

  • 用户信息端点,应用程序可以查询该端点以获取有关用户的更多信息。

启用OIDC不会影响您现有的OAuth 2.0流程,这些流程将继续与OIDC一起工作。

我们支持:

  • OpenID连接授权码流

  • OpenID连接隐式流

  • OpenID连接混合流

更有甚者 django-oauth-toolkit 还支持 OpenID Connect RP-Initiated Logout

配置

默认情况下不启用OIDC,因为它需要必须提供的其他配置。 django-oauth-toolkit 支持两种不同的JWT令牌签名算法, RS256 ,它使用非对称RSA密钥(公钥和私钥),以及 HS256 ,它使用对称密钥。

更可取的是使用 RS256 ,因为这会产生一个可由任何人使用公钥进行验证的令牌(公钥由随附的OIDC服务自动发现提供并可被发现 django-oauth-toolkit )。 HS256 另一方面,使用 client_secret 以便验证密钥。这更易于实现,但会增加安全验证令牌的难度。

vbl.使用 HS256 还意味着您不能使用隐式流或混合流,也不能在公共客户端中验证令牌,因为您不能公开 client_secret 给一个公共客户。如果您正在使用公共客户端,则必须使用 RS256

创建RSA私钥

使用 RS256 需要RSA私钥,用于对JWT进行签名。您可以使用 openssl 工具::

openssl genrsa -out oidc.key 4096

这将生成4096位RSA密钥,这将足以满足我们的需求。

警告

此密钥的内容 must 要保守秘密。不要将其放在您的设置中,并将其提交给版本控制!

如果密钥被意外泄露,攻击者可以使用它来伪造JWT令牌,以验证您的OAuth提供程序是否发布,这是非常糟糕的!

如果被泄露了,你应该立即更换钥匙。

处理它的安全方法是:

  • 将其存储在安全系统中,如 Hashicorp Vault ,并在运行服务器时将其注入到您的环境中。

  • 将其存储在服务器上的安全文件中,并使用初始化脚本将其注入到您的环境中。

现在我们需要将该密钥添加到我们的设置中,并允许 openid 要使用的范围。假设我们已经设置了一个名为 OIDC_RSA_PRIVATE_KEY ,我们可以对我们的 settings.py **

import os

OAUTH2_PROVIDER = {
    "OIDC_ENABLED": True,
    "OIDC_RSA_PRIVATE_KEY": os.environ.get("OIDC_RSA_PRIVATE_KEY"),
    "SCOPES": {
        "openid": "OpenID Connect scope",
        # ... any other scopes that you use
    },
    # ... any other settings you want
}

如果您要将OIDC支持添加到现有的OAuth 2.0提供程序站点,并且您当前正在使用的自定义类 OAUTH2_SERVER_CLASS ,则必须将此类更改为派生自 oauthlib.openid.Server 而不是 oauthlib.oauth2.Server

使用 RSA 密钥对,可以从私钥生成公钥,因此不需要为公钥添加设置。

轮换RSA私钥

可以在jwks_uri中使用 OIDC_RSA_PRIVATE_KEYS_INACTIVE 布景。例如:

OAUTH2_PROVIDER = {
    "OIDC_RSA_PRIVATE_KEY": os.environ.get("OIDC_RSA_PRIVATE_KEY"),
    "OIDC_RSA_PRIVATE_KEYS_INACTIVE": [
        os.environ.get("OIDC_RSA_PRIVATE_KEY_2"),
        os.environ.get("OIDC_RSA_PRIVATE_KEY_3")
    ]
    # ... other settings
}

要旋转,请执行以下步骤:

  1. 生成新密钥,并将其添加到非活动集中。然后部署应用程序。

  2. 交换活动密钥和非活动密钥,然后重新部署。

  3. 在一段合理的时间后,删除非活动密钥。至少,你应该等待 ID_TOKEN_EXPIRE_SECONDS 以确保在有效令牌到期之前不会删除密钥。

vbl.使用 HS256 钥匙

如果您更喜欢使用 HS256 密钥,您不需要创建任何额外的密钥, django-oauth-toolkit 将只使用应用程序的 client_secret 来签署JWT令牌。

来验证JWT的签名 client_secret ,则必须设置应用程序的 hash_client_secretFalse

在这种情况下,您只需启用OIDC并添加 openid 添加到您的 settings.py **

OAUTH2_PROVIDER = {
    "OIDC_ENABLED": True,
    "SCOPES": {
        "openid": "OpenID Connect scope",
        # ... any other scopes that you use
    },
    # ... any other settings you want
}

备注

如果要启用 RS256 以后,您可以这样做--只需如上所述添加私钥。

RP启动的注销

此功能必须单独启用,因为它是核心标准的扩展。

OAUTH2_PROVIDER = {
    # OIDC has to be enabled to use RP-Initiated Logout
    "OIDC_ENABLED": True,
    # Enable and configure RP-Initiated Logout
    "OIDC_RP_INITIATED_LOGOUT_ENABLED": True,
    "OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT": True,
    # ... any other settings you want
}

设置启用OIDC的客户端

在中设置OIDC客户端 django-oauth-toolkit 很简单-事实上,所有已配置的现有OAuth 2.0授权代码流和隐式流应用程序都可以通过设置适当的算法来轻松更新以使用OIDC。

您还可以通过更改现有应用的授权授予类型并选择要使用的签名算法,将现有应用切换为使用OIDC混合流。

中可以了解不同流程的利弊。 this excellent article 罗伯特·布罗克尔曼写的。

OIDC授权码流

要创建OIDC授权代码流客户端,请创建 Application 使用授权类型 Authorization code 并选择您想要的签名算法。

在提出授权请求时,请确保包括 openid 作为一个观察镜。当代码被交换为访问令牌时,响应还将包含ID令牌JWT。

如果 openid 范围未被请求,授权请求将被视为标准OAuth 2.0授权码授予请求。

使用 PKCE 启用后,即使是公共客户端也可以使用此流,并且它是最安全和最推荐的流。

OIDC隐含流

OIDC隐式流与OAuth 2.0隐式授予非常相似,不同之处在于客户端可以请求 response_typeid_tokenid_token token 。请求公正 token 也是可能的,但它将使其不是OIDC流,并将退回到与OAuth 2.0隐式Grant相同的状态。

To setup an OIDC Implicit Flow client, simply create an Application with the a grant type of Implicit and select your desired signing algorithm, and configure the client to request the openid scope and an OIDC response_type (id_token or id_token token).

OIDC混合流

OIDC混合流是前两个流的混合。它允许ID令牌和访问令牌返回到前端,同时还允许后端在后端检索ID令牌和访问令牌(不一定是相同的访问令牌)。

要设置OIDC混合流应用程序,请创建 Application 授予类型为 OpenID connect hybrid 并选择您想要的签名算法。

自定义OIDC响应

此基本配置将为您提供基本的工作OIDC设置,但您的ID令牌中几乎没有声明,并且 UserInfo 服务将只返回与ID令牌相同的声明。

要配置所有这些内容,我们需要自定义 OAUTH2_VALIDATOR_CLASS 在……里面 django-oauth-toolkit 。在我们的项目中创建一个新文件,例如 my_project/oauth_validators.py **

from oauth2_provider.oauth2_validators import OAuth2Validator


class CustomOAuth2Validator(OAuth2Validator):
    pass

然后配置我们的站点以在我们的 settings.py **

OAUTH2_PROVIDER = {
    "OAUTH2_VALIDATOR_CLASS": "my_project.oauth_validators.CustomOAuth2Validator",
    # ... other settings
}

现在,我们可以通过向定制验证器添加方法来定制令牌和响应。

将索赔添加到ID令牌

默认情况下,ID令牌将只有一个 sub 索赔(除所要求的索赔外,如 issaudexpiatauth_time 等),以及 sub Claim将使用用户的主键作为值。您可能希望对此进行自定义,并添加其他声明或更改发送给 sub 认领。为此,您需要向我们的定制验证器添加一个方法。它有两种形式之一:

第一个表单传递一个请求对象,应该返回一个将索赔名称映射到索赔数据的字典::

class CustomOAuth2Validator(OAuth2Validator):
    # Set `oidc_claim_scope = None` to ignore scopes that limit which claims to return,
    # otherwise the OIDC standard scopes are used.

    def get_additional_claims(self, request):
        return {
            "given_name": request.user.first_name,
            "family_name": request.user.last_name,
            "name": ' '.join([request.user.first_name, request.user.last_name]),
            "preferred_username": request.user.username,
            "email": request.user.email,
        }

第二个表单没有获取请求对象,应该返回一个将声明名称映射到可调用对象的字典,接受请求并生成声明数据::

class CustomOAuth2Validator(OAuth2Validator):
    # Extend the standard scopes to add a new "permissions" scope
    # which returns a "permissions" claim:
    oidc_claim_scope = OAuth2Validator.oidc_claim_scope
    oidc_claim_scope.update({"permissions": "permissions"})

    def get_additional_claims(self):
        return {
            "given_name": lambda request: request.user.first_name,
            "family_name": lambda request: request.user.last_name,
            "name": lambda request: ' '.join([request.user.first_name, request.user.last_name]),
            "preferred_username": lambda request: request.user.username,
            "email": lambda request: request.user.email,
            "permissions": lambda request: list(request.user.get_group_permissions()),
        }

标准索赔 sub 默认情况下包含,以删除它的覆盖 get_claim_dict

支持的索赔发现

为了帮助客户及早发现索赔,可以在发现信息中的 claims_supported 钥匙。为了让发现信息视图自动添加您的验证器返回的所有声明,您需要使用第二个表单(生成callable),因为发现信息视图是通过未经身份验证的请求请求的,因此直接生成声明数据将失败。如果您使用第一个表单,直接生成索赔数据,您的索赔将不会添加到发现信息中。

在某些情况下,最好不要在发现信息中列出所有声明。若要自定义公布哪些索赔,可以重写 get_discovery_claims 方法返回要公布的声明名称列表。如果你的 get_additional_claims 使用第一个表单,并且您仍想公布索赔,您也可以覆盖 get_discovery_claims

使用OIDC作用域确定返回哪些声明

这个 oidc_claim_scope OAuth2Validator类属性实现OIDC的 5.4 Requesting Claims using Scope Values 特写。例如,一个 given_name 只有在以下情况下才会退回索赔 profile 已授予作用域。

要更改声明列表以及返回声明的作用域,请重写 oidc_claim_scope 以声明为关键字的值为Scope的字典。下面的示例添加指令以返回 foo 在以下情况下索赔 bar 范围已授予::

class CustomOAuth2Validator(OAuth2Validator):
    oidc_claim_scope = OAuth2Validator.oidc_claim_scope
    oidc_claim_scope.update({"foo": "bar"})

oidc_claim_scope = None 返回所有声明,而不考虑授予的作用域。

您必须确保已通过以下方式添加其他报销申请 get_additional_claims 并定义了 OAUTH2_PROVIDER["SCOPES"] 在您的设置中,才能使用此功能。

备注

request 对象不是 django.http.Request 对象,但一个 oauthlib.common.Request 对象。这具有许多属性,您可以使用这些属性来确定要将什么声明放入ID令牌:

您决定在令牌中输入什么声明取决于您根据作用域和/或声明对您的提供者意味着什么来确定。

将信息添加到 UserInfo 服务

这个 UserInfo 服务作为OIDC服务的一部分提供,用于检索有关给定访问令牌的用户的信息。使用该服务是可选的。要访问该服务,请向 UserInfo 端点,例如 /o/userinfo/ 并将登录时检索到的访问令牌作为 Bearer 令牌或作为表单编码的 access_token POST请求的正文参数。

同样,要修改交付的内容,我们需要向定制验证器添加一个函数。默认实现添加来自ID令牌的声明,因此您可能希望重新使用该::

class CustomOAuth2Validator(OAuth2Validator):

    def get_userinfo_claims(self, request):
        claims = super().get_userinfo_claims(request)
        claims["color_scheme"] = get_color_scheme(request.user)
        return claims

自定义登录流程

客户端可以在每次向 /authorize 在OIDC授权代码流期间通过添加 prompt=login 查询参数和值。仅限 login 当前支持。请参阅OIDC的 3.1.2.1 Authentication Request 了解更多细节。

OIDC视图

启用OIDC支持会将三个视图添加到 django-oauth-toolkit 。如果未启用OIDC,这些视图将记录未启用OIDC支持,并返回 404 响应,或者如果 DEBUG 已启用,则引发 ImproperlyConfigured 例外。

在下面的文档中,它假设您已经装载了 django-oauth-toolkit 在… /o/ 。如果您已将其挂载到其他位置,请相应地调整URL。

ConnectDiscoveryInfoView

可在以下位置获得 /o/.well-known/openid-configuration ,该视图向OIDC客户端提供自动发现信息,告诉它们要使用的JWT颁发者、要用来验证JWT的JWK的位置、要查询的令牌和用户信息端点,以及其他详细信息。

JwksInfoView

可在以下位置获得 /o/.well-known/jwks.json ,该视图提供了用于对为ID令牌生成的JWT进行签名的密钥的详细信息,以便客户端能够对其进行验证。

UserInfoView

可在以下位置获得 /o/userinfo/ ,此视图提供额外的用户详细信息。如上所述,您可以自定义响应中包含的详细信息。

RPInitiatedLogoutView

可在以下位置获得 /o/rp-initiated-logout/ ,此视图允许 Client (依赖方)要求 Resource Owner 已注销,地址为 Authorization Server (OpenID提供程序)。