创建提供程序

OAuthLib是一个无依赖项的库,可以与任何Web框架一起使用。也就是说,有一些特定于框架的帮助器库可以让您的工作更轻松。

如果没有对您最喜欢的框架的支持,而您有兴趣提供它,那么您来对了地方。OAuthLib可以处理OAuth逻辑,让您支持几个框架和设置特定任务,例如将请求对象编组到URI、头和正文参数中,并为后端提供存储令牌、客户端等的接口。

1.OAuth2.0提供程序流程

Web框架和提供者实现之间的OAuthLib接口并不总是容易遵循,这就是为什么下面的图表是为了更好地理解OAuthLib在请求生命周期中的含义。

digraph oauthlib_legend {

    subgraph cluster_legend {
        label="Legend";

        /*
        method [ shape=record; label="{{RequestValidator\nmethod name|arguments}|return values}" ];
        endpoint [ shape=record; label="{Endpoint name|{function name|arguments}|grant type}" ];
        webframework [ shape=hexagon; label="Upstream functions" ];
        */

        flow_code_token [shape=none,label="Authorization Code\nAccess Token Request"];
        flow_code_auth [shape=none,label="Authorization Code\nAuthorization Request"];
        flow_implicit [shape=none,label="Implicit Grant"];
        flow_password [shape=none,label="Resource Owner Password\nCredentials Grant"];
        flow_clicreds [shape=none,label="Client Credentials Grant"];
        flow_refresh [shape=none,label="Refresh Grant"];
        flow_introspect [shape=none,label="Token Introspection"];
        flow_revoke [shape=none,label="Token Revoke"];
        flow_resource [shape=none,label="Resource Access"];
        flow_code_token -> a [style=bold,color=darkgreen];
        flow_code_auth -> b [style=bold,color=green];
        flow_implicit -> c [style=bold,color=orange];
        flow_password -> d [style=bold,color=red];
        flow_clicreds -> e [style=bold,color=blue];
        flow_refresh -> f [style=bold,color=brown];
        flow_introspect -> g [style=bold,color=yellow];
        flow_revoke -> h [style=bold,color=purple];
        flow_resource -> i [style=bold,color=pink];
        a, b, c, d, e, f, g, h, i [shape=none,label=""];
    }
}
digraph oauthlib {
    /* Naming conventions:
    f_ : functions in shape=record
    endpoint_ : endpoints in shape=record
    webapi_ : oauthlib entry/exit points in shape=hexagon
    if_ : internal conditions
    r_ : used when returning from two functions into one for improving clarity
    h_ : callbacks/hooks available but not required
    */
    center="1"
    edge [ style=bold ];

    /* Web Framework Entry and Exit points */
    {
        node [ shape=hexagon ];
        edge [ style=normal ];
        
        webapi_request [ label="WebFramework\nHTTP request" ];
        webapi_request:s ->
                endpoint_authorize:top:n,
                endpoint_token:top:n,
                endpoint_introspect:top:n,
                endpoint_revoke:top:n,
                endpoint_resource:top:n;
        webapi_response [ label="WebFramework\nHTTP response" ];
    }

    /* OAuthlib Endpoints */
    {
        rank=same;

        endpoint_authorize [ shape=record; label="{<top>Authorize Endpoint|{create_authorize_response|{uri|method|body|headers|credentials}}|{<token>token|<code>code}}" ];
        endpoint_token [ shape=record; label="{<top>Token Endpoint|{create_token_response|{uri|method|body|headers|credentials}}|{<authorization_code>authorization_code|<password>password|<client_credentials>client_credentials|<refresh_token>refresh_token}}" ];
        endpoint_revoke [ shape=record; label="{<top>Revocation Endpoint|{create_revocation_response|{uri|method|body|headers}}}" ];
        endpoint_introspect [ shape=record; label="{<top>Introspect Endpoint|{create_introspect_response|{uri|method|body|headers}}}" ];
        endpoint_resource [ shape=record; label="{<top>Resource Endpoint|{verify_request|{uri|method|body|headers|scopes_list}}}" ];
    }

    /* OAuthlib RequestValidator Methods */
    {
        node [ shape=record ];

        f_client_authentication_required [ label="{{<top>client_authentication_required|request}|{<true>True|<false>False}}"; ];
        f_authenticate_client [ label="{{<top>authenticate_client|request}|{<true>True|<false>False}}";];
        f_authenticate_client_id [ label="{{<top>authenticate_client_id|{client_id|request}}|{<true>True|<false>False}}"; ];
        f_validate_grant_type [ label="{{<top>validate_grant_type|{client_id|grant_type|client|request}}|{<true>True|<false>False}}"; ];
        f_validate_code [ label="{{<top>validate_code|{client_id|code|request}}|{<true>True|<false>False}}"; ];
        f_confirm_redirect_uri [ label="{{<top>confirm_redirect_uri|{client_id|code|redirect_uri|client|request}}|{<true>True|<false>False}}"; ];
        f_get_default_redirect_uri [ label="{{<top>get_default_redirect_uri|{client_id|request}}|{<redirect_uri>redirect_uri|<none>None}}"; ];
        f_invalidate_authorization_code [ label="{{<top>invalidate_authorization_code|{client_id|code|request}}|None}"; ];
        f_validate_scopes [ label="{{<top>validate_scopes|{client_id|scopes|client|request}}|{<true>True|<false>False}}"; ];
        f_save_bearer_token [ label="{{<top>save_bearer_token|{token|request}}|None}"; ];
        f_revoke_token [ label="{{<top>revoke_token|{token|token_type_hint|request}}|None}"; ];
        f_validate_client_id [ label="{{<top>validate_client_id|{client_id|request}}|{<true>True|<false>False}}"; ];
        f_validate_redirect_uri [ label="{{<top>validate_redirect_uri|{client_id|redirect_uri|request}}|{<true>True|<false>False}}"; ];
        f_is_pkce_required [ label="{{<top>is_pkce_required|{client_id|request}}|{<true>True|<false>False}}"; ];
        f_validate_response_type [ label="{{<top>validate_response_type|{client_id|response_type|client|request}}|{<true>True|<false>False}}"; ];
        f_save_authorization_code [ label="{{<top>save_authorization_code|{client_id|code|request}}|None}"; ];
        f_validate_bearer_token [ label="{{<top>validate_bearer_token|{token|scopes|request}}|{<true>True|<false>False}}"; ];
        f_validate_refresh_token [ label="{{<top>validate_refresh_token|{refresh_token|client|request}}|{<true>True|<false>False}}"; ];
        f_get_default_scopes [ label="{{<top>get_default_scopes|{client_id|request}}|{<scopes>[scopes]}}"; ];
        f_get_original_scopes [ label="{{<top>get_original_scopes|{refresh_token|request}}|{<scopes>[scopes]}}"; ];
        f_is_within_original_scope [ label="{{<top>is_within_original_scope|{refresh_scopes|refresh_token|request}}|{<true>True|<false>False}}"; ];
        f_validate_user [ label="{{<top>validate_user|{username|password|client|request}}|{<true>True|<false>False}}"; ];
        f_introspect_token [ label="{{<top>introspect_token|{token|token_type_hint|request}}|{<claims>\{claims\}|<none>None}}"; ];
        f_rotate_refresh_token [ label="{{<top>rotate_refresh_token|{request}}|{<true>True|<false>False}}"; ];
    }

    /* OAuthlib Conditions */

    if_code_challenge [ label="if code_challenge"; ];
    if_redirect_uri [ label="if redirect_uri"; ];
    if_redirect_uri_present [ shape=none;label="present"; ];
    if_redirect_uri_missing [ shape=none;label="missing"; ];
    if_scopes [ label="if scopes"; ];
    if_all [ label="all(request_scopes not in scopes)"; ];

    /* OAuthlib functions returns helpers */
    r_client_authenticated [ shape=none,label="client authenticated"; ];

    /* OAuthlib errors */
    e_normal [ shape=none,label="ERROR" ];

    /* Ranking by functional roles */
    {
        rank = same;
        f_validate_client_id;
        f_validate_code;
        /* f_validate_user; */
        f_validate_bearer_token;
        f_validate_refresh_token;
        f_introspect_token;
        f_revoke_token;
    }
    {
        rank = same;
        f_validate_redirect_uri;
        f_confirm_redirect_uri;
    }
    {
        rank = same;
        f_save_bearer_token;
        f_save_authorization_code;
    }
    {
        rank = same;
        f_invalidate_authorization_code;
    }
    {
        rank = same;
        f_validate_scopes;
        f_get_original_scopes;
        f_get_default_scopes;
    }
    {
        rank = same;
        f_is_within_original_scope;
    }

    {
        node [ shape=record,color=grey ];
        edge [ color=grey ];

        h_pre_auth [ label="{{<top>pre_auth|<arg>request}|<resp>\{credentials\}}}"; ];
        h_post_auth [ label="{{<top>post_auth|<arg>request}|<resp>\{credentials\}}}"; ];
        h_pre_token [ label="{{<top>pre_token|<arg>request}|<resp>}}"; ];
        h_pre_token_password [ label="{{<top>pre_token|<arg>request}|<resp>}}"; ];
        h_pre_token_implicit [ label="{{<top>pre_token|<arg>request}|<resp>}}"; ];
        h_post_token [ label="{{<top>post_token|<arg>request}|<resp>}}"; ];
        h_token_modifiers [ label="{{<top>token_modifiers|{token|token_handler|<arg>request}}|<resp>\{token\}}}"; ];
        h_code_modifiers [ label="{{<top>code_modifiers|{grant|token_handler|<arg>request}}|<resp>\{grant\}}}"; ];
        h_generate_access_token [ label="{{<top>generate_access_token|<arg>request}|<resp>\{access token\}}}"; ];
        h_generate_refresh_token [ label="{{<top>generate_refresh_token|<arg>request}|<resp>\{refresh token\}}}"; ];

        h_pre_auth:resp:se -> h_pre_auth:arg:ne;
        h_post_auth:resp:se -> h_post_auth:arg:ne;
        h_pre_token:resp:se -> h_pre_token:arg:ne;
        h_pre_token_password:resp:se -> h_pre_token_password:arg:ne;
        h_pre_token_implicit:resp:se -> h_pre_token_implicit:arg:ne;
        h_post_token:resp:se -> h_post_token:arg:ne;
        h_token_modifiers:resp:se -> h_token_modifiers:arg:ne;
        h_code_modifiers:resp:se -> h_code_modifiers:arg:ne;
    }
    {
            rank = same;
            h_token_modifiers;
            h_code_modifiers;
    }

    /* Authorization Code - Access Token Request */
    {
        edge [ color=darkgreen ];

        endpoint_token:authorization_code:s -> h_pre_token -> f_client_authentication_required;
        f_client_authentication_required:true:s -> f_authenticate_client;
        f_client_authentication_required:false:s -> f_authenticate_client_id;
        f_authenticate_client:true:s -> r_client_authenticated [ arrowhead=none ];
        f_authenticate_client_id:true:s -> r_client_authenticated [ arrowhead=none ];
        r_client_authenticated -> f_validate_grant_type;
        f_validate_grant_type:true:s -> f_validate_code;

        f_validate_code:true:s -> if_redirect_uri;
        if_redirect_uri -> if_redirect_uri_present [ arrowhead=none ];
        if_redirect_uri -> if_redirect_uri_missing [ arrowhead=none ];
        if_redirect_uri_present -> f_confirm_redirect_uri;
        if_redirect_uri_missing -> f_get_default_redirect_uri;
        f_get_default_redirect_uri:redirect_uri:s -> f_confirm_redirect_uri;

        f_confirm_redirect_uri:true:s -> h_post_token;

        h_post_token -> h_generate_access_token -> f_rotate_refresh_token;
        f_rotate_refresh_token:true:s -> h_generate_refresh_token -> h_token_modifiers;
        f_rotate_refresh_token:false:s -> h_token_modifiers;
        h_token_modifiers -> f_save_bearer_token ->
        f_invalidate_authorization_code -> webapi_response;
    }
    /* Authorization Code - Authorization Request */
    {
        edge [ color=green ];

        endpoint_authorize:code:s -> f_validate_client_id;
        f_validate_client_id:true:s -> if_redirect_uri;
        if_redirect_uri -> if_redirect_uri_present [ arrowhead=none ];
        if_redirect_uri -> if_redirect_uri_missing [ arrowhead=none ];
        if_redirect_uri_present -> f_validate_redirect_uri;
        if_redirect_uri_missing -> f_get_default_redirect_uri;

        f_validate_redirect_uri:true:s -> h_pre_auth;
        f_get_default_redirect_uri:redirect_uri:s -> h_pre_auth;
        h_pre_auth -> f_validate_response_type;
        f_validate_response_type:true:s -> f_is_pkce_required;
        f_is_pkce_required:true:s -> if_code_challenge;
        f_is_pkce_required:false:s -> f_validate_scopes;

        if_code_challenge -> f_validate_scopes [ label="present" ];
        if_code_challenge -> e_normal [ label="missing",style=dashed ];

        f_validate_scopes:true:s -> h_post_auth;
        h_post_auth -> h_code_modifiers -> f_save_authorization_code;
        f_save_authorization_code -> webapi_response;
    }

    /* Implicit */ 
    {
        edge [ color=orange ];

        endpoint_authorize:token:s -> f_validate_client_id;
        f_validate_client_id:true:s -> if_redirect_uri;
        if_redirect_uri -> if_redirect_uri_present [ arrowhead=none ];
        if_redirect_uri -> if_redirect_uri_missing [ arrowhead=none ];
        if_redirect_uri_present -> f_validate_redirect_uri;
        if_redirect_uri_missing -> f_get_default_redirect_uri;

        f_validate_redirect_uri:true:s -> h_pre_auth;
        f_get_default_redirect_uri:redirect_uri:s -> h_pre_auth;
        h_pre_auth -> h_pre_token_implicit -> f_validate_response_type;

        f_validate_response_type:true:s -> f_validate_scopes;
        f_validate_scopes:true:s -> h_post_auth -> h_post_token ->
        h_generate_access_token -> h_token_modifiers ->
        f_save_bearer_token -> webapi_response;
    }

    /* Resource Owner Password Grant */
    {
        edge [ color=red ];

        endpoint_token:password:s -> f_client_authentication_required;
        f_client_authentication_required:true:s -> f_authenticate_client;
        f_client_authentication_required:false:s -> f_authenticate_client_id;
        f_authenticate_client:true:s -> r_client_authenticated [ arrowhead=none ];
        f_authenticate_client_id:true:s -> r_client_authenticated [ arrowhead=none ];
        r_client_authenticated -> h_pre_token_password -> f_validate_user;
        f_validate_user:true:s -> f_validate_grant_type;

        f_validate_grant_type:true:s -> if_scopes;
        if_scopes -> f_validate_scopes [ label="present" ];
        if_scopes -> f_get_default_scopes [ label="missing" ];

        f_validate_scopes:true:s -> h_post_token;
        f_get_default_scopes -> h_post_token;

        h_post_token -> h_generate_access_token -> f_rotate_refresh_token;
        f_rotate_refresh_token:true:s -> h_generate_refresh_token -> h_token_modifiers;
        f_rotate_refresh_token:false:s -> h_token_modifiers ->
        f_save_bearer_token -> webapi_response;
    }

    /* Client Credentials Grant */
    {
        edge [ color=blue ];

        endpoint_token:client_credentials:s -> h_pre_token -> f_authenticate_client;

        f_authenticate_client:true:s -> f_validate_grant_type;
        f_validate_grant_type:true:s -> f_validate_scopes;
        f_validate_scopes:true:s -> h_post_token;

        h_post_token -> h_generate_access_token -> h_token_modifiers ->
        f_save_bearer_token -> webapi_response;
    }

    /* Refresh Grant */
    {
        edge [ color=brown ];

        endpoint_token:refresh_token:s -> h_pre_token -> f_client_authentication_required;
        f_client_authentication_required:true:s -> f_authenticate_client;
        f_client_authentication_required:false:s -> f_authenticate_client_id;
        f_authenticate_client:true:s -> r_client_authenticated [ arrowhead=none ];
        f_authenticate_client_id:true:s -> r_client_authenticated [ arrowhead=none ];
        r_client_authenticated -> f_validate_grant_type;

        f_validate_grant_type:true:s -> f_validate_refresh_token;
        f_validate_refresh_token:true:s -> f_get_original_scopes;
        f_get_original_scopes -> if_all;
        if_all -> f_is_within_original_scope [ label="True" ];
        if_all -> h_post_token [ label="False" ];
        f_is_within_original_scope:true:s -> h_post_token;
        h_post_token -> h_generate_access_token -> f_rotate_refresh_token;
        f_rotate_refresh_token:true:s -> h_generate_refresh_token -> h_token_modifiers;
        f_rotate_refresh_token:false:s -> h_token_modifiers;
        h_token_modifiers -> f_save_bearer_token -> webapi_response;
    }

    /* Introspect Endpoint  */
    {
        edge [ color=yellow ];

        endpoint_introspect:s -> f_client_authentication_required;
        f_client_authentication_required:true:s -> f_authenticate_client;
        f_client_authentication_required:false:s -> f_authenticate_client_id;
        f_authenticate_client:true:s -> r_client_authenticated [ arrowhead=none ];
        f_authenticate_client_id:true:s -> r_client_authenticated [ arrowhead=none ];
        r_client_authenticated -> f_introspect_token;
        f_introspect_token:claims -> webapi_response;
    }

    /* Revocation Endpoint */
    {
        edge [ color=purple ];

        endpoint_revoke:s -> f_client_authentication_required;
        f_client_authentication_required:true:s -> f_authenticate_client;
        f_client_authentication_required:false:s -> f_authenticate_client_id;
        f_authenticate_client:true:s -> r_client_authenticated [ arrowhead=none ];
        f_authenticate_client_id:true:s -> r_client_authenticated [ arrowhead=none ];
        r_client_authenticated -> f_revoke_token;
        f_revoke_token:s -> webapi_response;
    }

    /* Resource Access - Verify Request */
    {
        edge [ color=pink ];

        endpoint_resource:s -> f_validate_bearer_token;
        f_validate_bearer_token:true -> webapi_response;
    }
}

2.创建数据存储区模型

这些模型将代表各种特定于OAuth的概念。它们之间有一些重要的联系,OAuth的安全是基于这些联系的。下面是对模型的建议,以及为什么你需要某些属性。还有一些示例Django模型字段,它们应该可以直接转换为其他ORM,如SQLAlChemy和AppEngine数据存储。

用户(或资源所有者)

您站点的用户,根据用户的授权,客户端可以访问哪些资源。在我们的示例中,我们将重用django.contri.auth.delams中提供的用户模型。用户身份验证的方式与OAuth是正交的,并且可以是您喜欢的任何方式:

from django.contrib.auth.models import User

客户(或消费者)

对访问受保护资源感兴趣的客户端。

Client Identifier

必需的。客户端将在OAuth工作流期间使用的标识符。结构由您决定,可能是一个简单的UUID。

client_id = django.db.models.CharField(max_length=100, unique=True)

User

推荐。通常的做法是将每个客户端链接到现有用户之一。无论您是否关联客户端和用户,请确保您能够保护自己免受恶意客户端的攻击。

user = django.db.models.ForeignKey(User)

Grant Type

必需的。客户端可以使用的授权类型。每个客户端应该只有一个,因为每个授权类型都有不同的安全属性,最好将它们分开以避免错误。

# max_length and choices depend on which response types you support
grant_type = django.db.models.CharField(max_length=18,
choices=[('authorization_code', 'Authorization code')])

Response Type

如果使用授权类型和关联的响应类型(例如,授权码授权)或使用仅使用响应类型的授权(例如,隐式授予)。

# max_length and choices depend on which response types you support
response_type = django.db.models.CharField(max_length=4,
choices=[('code', 'Authorization code')])

Scopes

必需的。客户端可能请求访问的作用域列表。如果您允许多种类型的授权,这将因其不同的安全属性而有所不同。例如,隐式授权可能只允许只读作用域,但授权授权还允许写入。

# You could represent it either as a list of keys or by serializing
# the scopes into a string.
scopes = django.db.models.TextField()

# You might also want to mark a certain set of scopes as default
# scopes in case the client does not specify any in the authorization
default_scopes = django.db.models.TextField()

Redirect URIs

这些是客户端在授权后可以用来重定向到的绝对URI。您永远不应该允许客户端重定向到以前未注册的URI。

# You could represent the URIs either as a list of keys or by
# serializing them into a string.
redirect_uris = django.db.models.TextField()

# You might also want to mark a certain URI as default in case the
# client does not specify any in the authorization
default_redirect_uri = django.db.models.TextField()

承载令牌(OAuth 2标准令牌)

最常见的OAuth 2令牌类型。在文档中,这将被视为具有几个属性的对象,例如令牌类型和到期日期,并且不同于它所包含的访问令牌。将OAuth 2令牌视为容器,将访问令牌和刷新令牌视为文本。

Client

与向其提供令牌的客户端的关联。

client = django.db.models.ForeignKey(Client)

User

与此令牌向其授予访问权限的受保护资源的用户关联。

user = django.db.models.ForeignKey(User)

Scopes

令牌绑定到的作用域。尝试访问这些作用域之外的受保护资源将被拒绝。

# You could represent it either as a list of keys or by serializing
# the scopes into a string.
scopes = django.db.models.TextField()

Access Token

无法猜测的唯一字符串。

access_token = django.db.models.CharField(max_length=100, unique=True)

Refresh Token

无法猜测的唯一字符串。此令牌仅提供给机密客户端。例如授权码授权或资源所有者密码凭据授权。

refresh_token = django.db.models.CharField(max_length=100, unique=True)

Expiration time

确切的过期时间。通常情况下,这是在创建后一个小时。

expires_at = django.db.models.DateTimeField()

授权码

这是特定于授权码授予的,代表在成功授权时授予客户端的临时凭据。它稍后将被交换为访问令牌,当完成该操作时,它应该不复存在。它的寿命应该是有限的,不超过10分钟。此模型类似于持有者令牌,因为它主要充当属性的临时存储,以便稍后转移到令牌。

Client

与向其提供令牌的客户端的关联。

client = django.db.models.ForeignKey(Client)

User

与此令牌向其授予访问权限的受保护资源的用户关联。

user = django.db.models.ForeignKey(User)

Scopes

令牌绑定到的作用域。尝试访问这些作用域之外的受保护资源将被拒绝。

# You could represent it either as a list of keys or by serializing
# the scopes into a string.
scopes = django.db.models.TextField()

Redirect URI

根据RFC 6749第4.1节的规定,如果客户端在获取代码时指定了reDirect_uri,则该重定向URI必须绑定到代码,并在此方法中验证是否相等。此字段保存该绑定值。

redirect_uri = django.db.models.TextField()

Authorization Code

无法猜测的唯一字符串。

code = django.db.models.CharField(max_length=100, unique=True)

Expiration time

确切的过期时间。通常,这是在创建后10分钟内完成的。

expires_at = django.db.models.DateTimeField()

PKCE Challenge (optional)

如果您想要支持PKCE,则必须将 code_challenge 以及一个 code_challenge_method 设置为实际的授权码。

challenge = django.db.models.CharField(max_length=128)
challenge_method = django.db.models.CharField(max_length=6)

3.实现验证器

实现OAuth 2提供程序所涉及的大部分工作与将各种验证和持久性方法映射到存储后端有关。您需要实现的命名不太准确的接口称为 RequestValidator (欢迎提出命名建议)。

下面是一个非常基本的验证_客户端_id方法实现的示例。

from oauthlib.oauth2 import RequestValidator

# From the previous section on models
from my_models import Client

class MyRequestValidator(RequestValidator):

    def validate_client_id(self, client_id, request):
        try:
            Client.objects.get(client_id=client_id)
            return True
        except Client.DoesNotExist:
            return False

中提供了您需要实现的完整API RequestValidator 一节。您可能不需要实现所有方法,具体取决于您希望支持的授权类型。中提供了一个框架验证器,其中列出了WebApplicationServer所需的方法 examples GitHub上的文件夹。

相关章节包括:

4.创建您的复合端点

每个端点都可以彼此独立地工作,但是对于本例来说,更容易将它们视为一个单元。下面给出了预配置的All-in-One授权码授予端点的示例。

# From the previous section on validators
from my_validator import MyRequestValidator

from oauthlib.oauth2 import WebApplicationServer

validator = MyRequestValidator()
server = WebApplicationServer(validator)

相关章节包括:

5.创建您的端点视图

我们正在实施对授权代码授予的支持,因此需要授权的两个视图,即授权前和授权后以及令牌视图。我们还包括一个错误页面,如果客户端在他们的重定向中提供了无效的凭据,例如无效的重定向URI,则可以将用户重定向到该错误页面。

该示例使用Django,但应该可以移植到任何框架。

# Handles GET and POST requests to /authorize
class AuthorizationView(View):

    def __init__(self):
        # Using the server from previous section
        self._authorization_endpoint = server

    def get(self, request):
        # You need to define extract_params and make sure it does not
        # include file like objects waiting for input. In Django this
        # is request.META['wsgi.input'] and request.META['wsgi.errors']
        uri, http_method, body, headers = extract_params(request)

        try:
            scopes, credentials = self._authorization_endpoint.validate_authorization_request(
                uri, http_method, body, headers)

            # Not necessarily in session but they need to be
            # accessible in the POST view after form submit.
            request.session['oauth2_credentials'] = credentials

            # You probably want to render a template instead.
            response = HttpResponse()
            response.write('<h1> Authorize access to %s </h1>' % client_id)
            response.write('<form method="POST" action="/authorize">')
            for scope in scopes or []:
                response.write('<input type="checkbox" name="scopes" ' +
                'value="%s"/> %s' % (scope, scope))
                response.write('<input type="submit" value="Authorize"/>')
            return response

        # Errors that should be shown to the user on the provider website
        except errors.FatalClientError as e:
            return response_from_error(e)

        # Errors embedded in the redirect URI back to the client
        except errors.OAuth2Error as e:
            return HttpResponseRedirect(e.in_uri(e.redirect_uri))

    @csrf_exempt
    def post(self, request):
        uri, http_method, body, headers = extract_params(request)

        # The scopes the user actually authorized, i.e. checkboxes
        # that were selected.
        scopes = request.POST.getlist(['scopes'])

        # Extra credentials we need in the validator
        credentials = {'user': request.user}

        # The previously stored (in authorization GET view) credentials
        credentials.update(request.session.get('oauth2_credentials', {}))

        try:
            headers, body, status = self._authorization_endpoint.create_authorization_response(
            uri, http_method, body, headers, scopes, credentials)
            return response_from_return(headers, body, status)

        except errors.FatalClientError as e:
            return response_from_error(e)

# Handles requests to /token
class TokenView(View):

    def __init__(self):
        # Using the server from previous section
        self._token_endpoint = server

    def post(self, request):
        uri, http_method, body, headers = extract_params(request)

        # If you wish to include request specific extra credentials for
        # use in the validator, do so here.
        credentials = {'foo': 'bar'}

        headers, body, status = self._token_endpoint.create_token_response(
                uri, http_method, body, headers, credentials)

        # All requests to /token will return a json response, no redirection.
        return response_from_return(headers, body, status)

def response_from_return(headers, body, status):
    response = HttpResponse(content=body, status=status)
    for k, v in headers.items():
        response[k] = v
    return response

def response_from_error(e):
    return HttpResponseBadRequest('Evil client is unable to send a proper request. Error is: ' + e.description)

6.使用作用域保护您的API

让我们定义一个可以用来保护视图的装饰器。

class OAuth2ProviderDecorator(object):

    def __init__(self, resource_endpoint):
        self._resource_endpoint = resource_endpoint

    def protected_resource_view(self, scopes=None):
        def decorator(f):
            @functools.wraps(f)
            def wrapper(request):
                # Get the list of scopes
                try:
                    scopes_list = scopes(request)
                except TypeError:
                    scopes_list = scopes

                uri, http_method, body, headers = extract_params(request)

                valid, r = self._resource_endpoint.verify_request(
                        uri, http_method, body, headers, scopes_list)

                # For convenient parameter access in the view
                add_params(request, {
                    'client': r.client,
                    'user': r.user,
                    'scopes': r.scopes
                })
                if valid:
                    return f(request)
                else:
                    # Framework specific HTTP 403
                    return HttpResponseForbidden()
            return wrapper
        return decorator

provider = OAuth2ProviderDecorator(server)

此时,您已经准备好使用OAuth保护您的API视图了。花一些时间想出一组很好的作用域,因为它们在控制访问方面可能非常强大。

@provider.protected_resource_view(scopes=['images'])
def i_am_protected(request, client, resource_owner):
    # One of your many OAuth 2 protected resource views
    # Returns whatever you fancy
    # May be bound to various scopes of your choosing
    return HttpResponse('pictures of cats')

保护视图的一组作用域也可以在运行时由函数而不是列表动态配置。

def dynamic_scopes(request):
    # Place code here to dynamically determine the scopes
    # and return as a list
    return ['images']

@provider.protected_resource_view(scopes=dynamic_scopes)
def i_am_also_protected(request, client, resource_owner, **kwargs)
    # A view that has its views functionally set.
    return HttpResponse('pictures of cats')

7.让我们知道进展如何!

在我们的 Gitter OAuthLib community 或打开一个 GitHub issue =)

如果遇到问题,启用调试日志记录会很有帮助。

import logging
import oauthlib
import sys

oauthlib.set_debug(True)
log = logging.getLogger('oauthlib')
log.addHandler(logging.StreamHandler(sys.stdout))
log.setLevel(logging.DEBUG)