快速入门

使用Django、Django OAuth工具包和OAuthLib构建一个OAuth2提供程序。

我们将建造什么?

我们的计划是从头开始建立一个OAuth2提供商。

在此入门指南中,我们将:

  • 创建Django项目。

  • 安装和配置Django OAuth工具包。

  • 创建两个OAuth2应用程序。

  • 使用授权码授予流。

  • 使用客户端凭据授予流。

什么是OAuth?

OAuth是一种开放的访问授权标准,通常用于互联网用户授予网站或应用程序访问其他网站上的信息的权限,但不向他们提供密码。-- Whitson Gordon

姜戈

Django是一个高级的Python Web框架,它鼓励快速开发和干净、实用的设计。它由经验丰富的开发人员构建,解决了Web开发的大部分麻烦,因此您可以专注于编写应用程序,而不需要重新发明轮子。-- Django website

让我们从创建虚拟环境开始::

mkproject iam

这将创建、激活目录并将其切换到新的Python虚拟环境。

安装Django::

pip install Django

创建Django项目::

django-admin startproject iam

这将在当前目录中创建一个MySite目录。结构如下:

.
└── iam
    ├── iam
    │   ├── asgi.py
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── manage.py

创建Django应用程序::

cd iam/
python manage.py startapp users

这将创建一个目录 users ,其布局如下::

.
├── iam
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── users
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

如果您要开始一个新项目,强烈建议您设置一个自定义用户模型,即使默认情况下 User 型号对你来说就足够了。此模型的行为与默认用户模型相同,但如果将来需要,您可以对其进行定制。-- Django documentation

编辑 users/models.py 添加以下代码:

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

变化 iam/settings.py 要添加 users 适用于 INSTALLED_APPS

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users',
]

配置 users.User 作为用于 auth 应用程序通过添加 AUTH_USER_MODELiam/settings.py

AUTH_USER_MODEL = 'users.User'

为创建初始迁移 users 应用程序 User 型号::

python manage.py makemigrations

上面的命令将创建迁移::

Migrations for 'users':
  users/migrations/0001_initial.py
    - Create model User

最后执行迁移::

python manage.py migrate

这个 migrate 输出::

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, users
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying users.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying sessions.0001_initial... OK

Django OAuth工具包

Django OAuth工具包可以通过开箱即用地提供向Django项目添加OAuth2功能所需的所有端点、数据和逻辑来帮助您。

安装Django OAuth工具包::

pip install django-oauth-toolkit

增列 oauth2_providerINSTALLED_APPS 在……里面 iam/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users',
    'oauth2_provider',
]

执行迁移::

python manage.py migrate

这个 migrate 命令输出::

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, oauth2_provider, sessions, users
Running migrations:
  Applying oauth2_provider.0001_initial... OK
  Applying oauth2_provider.0002_auto_20190406_1805... OK

包括 oauth2_provider.urlsiam/urls.py 详情如下:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
]

这将使端点可用于授权、生成令牌和创建OAuth应用程序。

上次更改,添加 LOGIN_URLiam/settings.py

LOGIN_URL = '/admin/login/'

我们将使用Django管理员登录,让我们的生活变得轻松。

创建用户::

python manage.py createsuperuser

Username: wiliam
Email address: me@wiliam.dev
Password:
Password (again):
Superuser created successfully.

OAuth2授权授予

授权授权是表示资源所有者的授权(访问其受保护的资源)的凭据,由客户端用来获取访问令牌。-- RFC6749

OAuth框架为不同的用例指定了几种授权类型。-- Grant types

我们将首先尝试以下列出的赠款类型:

  • 授权码

  • 客户端凭据

这两种赠款类型涵盖了最初使用最多的用例。

授权码

授权码流最适合用于Web和移动应用程序。这是用于第三方集成的流程,用户授权您的合作伙伴在您的API中访问其产品。

启动开发服务器::

python manage.py runserver

将浏览器指向http://127.0.0.1:8000/o/applications/register/,让它创建一个应用程序。

如下面的截图所示填写表格,并在保存前记录 Client idClient secret ,我们将立即使用它。

如果您想将此应用程序与OIDC和 HS256 (见 OpenID Connect ),取消选中 Hash client secret 以允许使用JWT签名验证令牌。这意味着您的客户端机密将以明文存储,但这是成功使用签名JWT的唯一方法。

授权码申请注册

出口 Client idClient secret 作为环境变量的值:

export ID=vW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8
export SECRET=DZFpuNjRdt5xUEzxXovAp40bU3lQvoMvF3awEStn61RXWE0Ses4RgzHWKJKTvUCHfRkhcBi3ebsEfSjfEO96vo2Sh6pZlxJ6f7KcUbhvqMMPoVxRwv4vfdWEoWMGPeIO

现在,让我们使用PKCE(代码交换证明密钥)生成验证码授权,这对防止授权码注入很有用。为此,您必须首先生成一个 code_verifier 43到128个字符之间的随机字符串,然后对其进行编码以生成 code_challenge

import random
import string
import base64
import hashlib

code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))

code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')

注意到 code_challenge 因为我们将把它包含在代码流URL中。它应该看起来像这样 XRi41b-5yHtTojvCpXFpsLUnmGFz6xR15c3vpPANAvM

出口 code_verifier 值作为环境变量,它应该类似于:

export CODE_VERIFIER=N0hHRVk2WDNCUUFPQTIwVDNZWEpFSjI4UElNV1pSTlpRUFBXNTEzU0QzRTMzRE85WDFWTzU2WU9ESw==

要启动授权代码流,请转到此 URL 如下所示:

http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge=XRi41b-5yHtTojvCpXFpsLUnmGFz6xR15c3vpPANAvM&code_challenge_method=S256&client_id=vW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8&redirect_uri=http://127.0.0.1:8000/noexist/callback

请注意我们传递的参数:

  • response_typecode

  • code_challengeXRi41b-5yHtTojvCpXFpsLUnmGFz6xR15c3vpPANAvM

  • code_challenge_methodS256

  • client_idvW1RcAl7Mb0d5gyHNQIAcH110lWoOW2BmWJIero8

  • redirect_urihttp://127.0.0.1:8000/noexist/callback

这标识了您的应用程序,要求用户授权您的应用程序访问其资源。

继续并授权 web-app

授权码授权Web-APP

还记得我们用过 http://127.0.0.1:8000/noexist/callback AS redirect_uri 你会得到一个 Page not found (404) 但如果你得到一个像::这样的URL,它就会起作用

http://127.0.0.1:8000/noexist/callback?code=uVqLxiHDKIirldDZQfSnDsmYW1Abj2

这是OAuth2提供程序,试图为您提供 code 。在这种情况下 uVqLxiHDKIirldDZQfSnDsmYW1Abj2

将其导出为环境变量:

export CODE=uVqLxiHDKIirldDZQfSnDsmYW1Abj2

现在您有了用户授权,是时候获取访问令牌了::

curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id=${ID}" -d "client_secret=${SECRET}" -d "code=${CODE}" -d "code_verifier=${CODE_VERIFIER}" -d "redirect_uri=http://127.0.0.1:8000/noexist/callback" -d "grant_type=authorization_code"

为了更容易地可视化::

curl -X POST \
    -H "Cache-Control: no-cache" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    "http://127.0.0.1:8000/o/token/" \
    -d "client_id=${ID}" \
    -d "client_secret=${SECRET}" \
    -d "code=${CODE}" \
    -d "code_verifier=${CODE_VERIFIER}" \
    -d "redirect_uri=http://127.0.0.1:8000/noexist/callback" \
    -d "grant_type=authorization_code"

OAuth2提供程序将返回以下响应:

{
  "access_token": "jooqrnOrNa0BrNWlg68u9sl6SkdFZg",
  "expires_in": 36000,
  "token_type": "Bearer",
  "scope": "read write",
  "refresh_token": "HNvDQjjsnvDySaK0miwG4lttJEl9yD"
}

要访问用户资源,我们只需使用 access_token **

curl \
    -H "Authorization: Bearer jooqrnOrNa0BrNWlg68u9sl6SkdFZg" \
    -X GET http://localhost:8000/resource

客户端凭据

客户端凭据授予适用于机器对机器的身份验证。您可以授权您自己的服务或员工将银行帐户事务处理状态更改为已接受。

将浏览器指向http://127.0.0.1:8000/o/applications/register/,让它创建一个应用程序。

如下面截图所示填写表格,并在保存前记下 Client idClient secret 我们马上就会用到。

客户端凭据应用程序注册

出口 Client idClient secret 作为环境变量的值:

export ID=axXSSBVuvOyGVzh4PurvKaq5MHXMm7FtrHgDMi4u
export SECRET=1fuv5WVfR7A5BlF0o155H7s5bLgXlwWLhi3Y7pdJ9aJuCdl0XV5Cxgd0tri7nSzC80qyrovh8qFXFHgFAAc0ldPNn5ZYLanxSm1SI1rxlRrWUP591wpHDGa3pSpB6dCZ

客户端凭据流比授权码流更简单。

我们需要编码 client_idclient_secret 中编码的基于HTTP的身份验证 base64 我使用以下代码来实现这一点。

>>> import base64
>>> client_id = "axXSSBVuvOyGVzh4PurvKaq5MHXMm7FtrHgDMi4u"
>>> secret = "1fuv5WVfR7A5BlF0o155H7s5bLgXlwWLhi3Y7pdJ9aJuCdl0XV5Cxgd0tri7nSzC80qyrovh8qFXFHgFAAc0ldPNn5ZYLanxSm1SI1rxlRrWUP591wpHDGa3pSpB6dCZ"
>>> credential = "{0}:{1}".format(client_id, secret)
>>> base64.b64encode(credential.encode("utf-8"))
b'YXhYU1NCVnV2T3lHVnpoNFB1cnZLYXE1TUhYTW03RnRySGdETWk0dToxZnV2NVdWZlI3QTVCbEYwbzE1NUg3czViTGdYbHdXTGhpM1k3cGRKOWFKdUNkbDBYVjVDeGdkMHRyaTduU3pDODBxeXJvdmg4cUZYRkhnRkFBYzBsZFBObjVaWUxhbnhTbTFTSTFyeGxScldVUDU5MXdwSERHYTNwU3BCNmRDWg=='
>>>

将凭据导出为环境变量

export CREDENTIAL=YXhYU1NCVnV2T3lHVnpoNFB1cnZLYXE1TUhYTW03RnRySGdETWk0dToxZnV2NVdWZlI3QTVCbEYwbzE1NUg3czViTGdYbHdXTGhpM1k3cGRKOWFKdUNkbDBYVjVDeGdkMHRyaTduU3pDODBxeXJvdmg4cUZYRkhnRkFBYzBsZFBObjVaWUxhbnhTbTFTSTFyeGxScldVUDU5MXdwSERHYTNwU3BCNmRDWg==

要启动您调用的客户端凭据流 /token/ 终端直接::

curl -X POST -H "Authorization: Basic ${CREDENTIAL}" -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "grant_type=client_credentials"

要更容易地进行可视化::

curl -X POST \
    -H "Authorization: Basic ${CREDENTIAL}" \
    -H "Cache-Control: no-cache" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    "http://127.0.0.1:8000/o/token/" \
    -d "grant_type=client_credentials"

OAuth2提供程序将返回以下响应:

{
    "access_token": "PaZDOD5UwzbGOFsQr34LQ7JUYOj3yK",
    "expires_in": 36000,
    "token_type": "Bearer",
    "scope": "read write"
}

下一步是 first tutorial