Skip to main content

Django API

授权

验证访问令牌

安装依赖

requirements.txt中添加如下依赖并运行pip install -r requirements.txt.

cryptography~=2.8
django~=2.2.7
djangorestframework~=3.10.31
django-cors-headers~=3.1.1
drf-jwt~=1.13.3
pyjwt~=1.7.1
requests~=2.22.0

创建 Django 项目

django-admin startproject api_example
cd api_example
python manage.py startapp authok_authorization

添加 Django 远程用户

AuthenticationMiddleware后添加RemoteUserMiddleware.

api_example/settings.py
MIDDLEWARE = [
# ...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
]

添加ModelBackendRemoteUserBackend.

api_example/settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'django.contrib.auth.backends.RemoteUserBackend',
]

创建utils.py并定义以下方法把 AccessToken 中的 sub 映射为 username.

authok_authorization/utils.py
from django.contrib.auth import authenticate

def jwt_get_username_from_payload_handler(payload):
username = payload.get('sub').replace('|', '.')
authenticate(remote_user=username)
return username

验证访问令牌

添加rest_frameworkINSTALLED_APPS.

api_example/settings.py
INSTALLED_APPS = [
# ...
'rest_framework'
]

JSONWebTokenAuthentication添加到 Django REST 框架的 DEFAULT_AUTHENTICATION_CLASSES中:

api_example/settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}

通过JWT_AUTH变量来配置 Django REST Framework JWT:

api_example/settings.py
JWT_AUTH = {
'JWT_PAYLOAD_GET_USERNAME_HANDLER': 'authok_authorization.utils.jwt_get_username_from_payload_handler',
'JWT_DECODE_HANDLER': 'authok_authorization.utils.jwt_decode_token',
'JWT_ALGORITHM': 'RS256',
'JWT_AUDIENCE': 'undefined',
'JWT_ISSUER': 'https://YOUR_DOMAIN/',
'JWT_AUTH_HEADER_PREFIX': 'Bearer',
}

创建一个函数从 AuthOK 账号获取 JWKS, 对 Access Token 验证和解码

authok_authorization/utils.py
import json

import jwt
import requests

def jwt_decode_token(token):
header = jwt.get_unverified_header(token)
jwks = requests.get('https://{}/.well-known/jwks.json'.format('YOUR_DOMAIN')).json()
public_key = None
for jwk in jwks['keys']:
if jwk['kid'] == header['kid']:
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))

if public_key is None:
raise Exception('Public key not found.')

issuer = 'https://{}/'.format('YOUR_DOMAIN')
return jwt.decode(token, public_key, audience='undefined', issuer=issuer, algorithms=['RS256'])

验证Scope

authok_authorization/views.py
from functools import wraps
import jwt

from django.http import JsonResponse

def get_token_auth_header(request):
"""Obtains the Access Token from the Authorization Header
"""
auth = request.META.get("HTTP_AUTHORIZATION", None)
parts = auth.split()
token = parts[1]

return token

def requires_scope(required_scope):
"""Determines if the required scope is present in the Access Token
Args:
required_scope (str): The scope required to access the resource
"""
def require_scope(f):
@wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header(args[0])
decoded = jwt.decode(token, verify=False)
if decoded.get("scope"):
token_scopes = decoded["scope"].split()
for token_scope in token_scopes:
if token_scope == required_scope:
return f(*args, **kwargs)
response = JsonResponse({'message': 'You don\'t have access to this resource'})
response.status_code = 403
return response
return decorated
return require_scope

保护 API 端点

authok_authorization/views.py
from django.http import JsonResponse
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny

@api_view(['GET'])
@permission_classes([AllowAny])
def public(request):
return JsonResponse({'message': '公开端点! 未认证可见.'})


@api_view(['GET'])
def private(request):
return JsonResponse({'message': '私有端点! 认证后可见.'})

在需要验证scope的方法上使用requires_scope装饰器:

authok_authorization/views.py
@api_view(['GET'])
@requires_scope('read:messages')
def private_scoped(request):
return JsonResponse({'message': '私有端点! 需要认证并包含 read:messages scope'})

添加 URL映射

在应用目录创建urls.py, 添加URL映射:

authok_authorization/urls.py
from django.urls import path

from . import views

urlpatterns = [
path('api/public', views.public),
path('api/private', views.private),
path('api/private-scoped', views.private_scoped),
]

把 Django 工程的urls.py引用到应用的urls.py文件

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

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('authok_authorization.urls'))
]

使用 API

在应用中调用API

curl --request GET \
--url http://localhost:3010/api/private \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

获取访问令牌

在单页应用 或 移动端/原生应用中, 在授权成功后,你需要获取 访问令牌. 如何获取令牌以及如何调用API将取决于您正在开发的应用程序类型和使用的框架.

更多信息请参考相关应用程序快速入门:

curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data 'client_id=YOUR_CLIENT_ID' \
--data client_secret=YOUR_CLIENT_SECRET \
--data audience=YOUR_API_IDENTIFIER

测试API

1. 调用被保护端点

curl --request GET \
--url http://localhost:3010/api/private

以上调用会返回 401 HTTP (Unauthorized) 状态码.

携带 AccessToken 进行调用

curl --request GET \
--url http://localhost:3010/api/private \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

此时,会返回成功响应.

2. 调用被作用域保护的端点

curl --request GET \
--url http://localhost:3010/api/private-scoped \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'