本文为 django-oauth-toolkit 使用记录。(官方文档)
参考阅读:OAuth 2.0 的四种方式
1. 安装
pip install django-oauth-toolkit djangorestframework
2. 使用
创建 Django 项目,目录树如下图:
│ manage.py
├─provider
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
└─users
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
└─__init__.py
并进行相关配置:
# provider/settings.py
INSTALLED_APPS = [
...
'oauth2_provider', # add
'users', # add
'rest_framework', # add
]
LOGIN_URL = '/admin/login/'
OAUTH2_PROVIDER = {
'SCOPES': {
'read': 'Read scope',
'write': 'Write scope',
'test': 'test api',
'introspection': 'Introspect token scope', # 认证 和 资源服务器分离时使用
}
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication', # 使用OAuth登录认证
)
}
# provider/urls.py
urlpatterns = [
...
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), # add
]
3. 编写 API 接口
本次项目中的 User 模型使用 Django 默认的模型。
# users/views.py
from django.contrib.auth.models import User
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope
from rest_framework import generics, permissions, serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', "first_name", "last_name")
class UserList(generics.ListCreateAPIView):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
required_scopes = ['test']
class UserDetails(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
required_scopes = ['test']
# provider/urls.py
from users.views import UserList # add
urlpatterns = [
...
path('users/', UserList.as_view()), # add
path('users/<pk>/', UserDetails.as_view()), # add
]
同步数据库、创建超级用户并运行项目:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver 0.0.0.0:8000
4. 注册/添加 Application
访问 http://127.0.0.1:8000/o/applications/ 注册/添加 Application 。
其中 client_id 和 client_secret 可以自定义,client_type 为 confidential,grant_type 为 password;添加后如下图所示。
5. 访问 API 接口
以下接口测试均使用 python httpie 工具。(pip install httpie)
5.1 获取 access_token (登录)
其中 client_id 和 client_secret 为注册 Application 时填写的内容;username 及 password 为 Django User 模型中的用户名及密码。
# 其中 client_id 和 client_secret 为注册 Application 时填写的内容
# username 及 password 为 Django User 模型中的用户名及密码
http -f POST http://127.0.0.1:8000/o/token/ client_id=self client_secret=self grant_type=password username=admin password=123456
# return
{
"access_token": "LpyYoQvzPZc2wNGc2MPNM0wkYuXNZt",
"expires_in": 36000,
"refresh_token": "qFcUD4Jvj6k1R3xpLWdnrfMRuFfEom",
"scope": "read write",
"token_type": "Bearer"
}
5.2 获取 users 列表数据
访问数据时需要将 access_token 添加到请求头中。
# 其中 Bearer 后为 access_token
http -f POST http://127.0.0.1:8000/users/ Authorization:"Bearer LpyYoQvzPZc2wNGc2MPNM0wkYuXNZt"
# return
[
{
"email": "",
"first_name": "",
"last_name": "",
"username": "admin"
}
]
5.3 刷新 access_token
其中 client_id 和 client_secret 为注册 Application 时填写的内容;
grant_type 类型为 refresh_token,意为刷新令牌;
refresh_token 即为 5.1步骤 获取到的刷新令牌。
# 其中 client_id 和 client_secret 为注册 Application 时填写的内容
# refresh_token 即为 5.1步骤 获取到的刷新令牌
http -f POST http://127.0.0.1:8000/o/token/ refresh_token=qFcUD4Jvj6k1R3xpLWdnrfMRuFfEom client_id=self client_secret=self grant_type=refresh_token
# return
{
"access_token": "GJSkO5nRpfyOanF78MOVlcyGScIWNP",
"expires_in": 36000,
"refresh_token": "LfwrybnMAsAwdGRx0GBot6oMFZGBbS",
"scope": "read write",
"token_type": "Bearer"
}
5.4 注销/移除 access_token (退出登录)
其中 client_id 和 client_secret 为注册 Application 时填写的内容;
grant_type 类型为 revoke_token,意为移除令牌;
token 为有效的 access_token。
# 其中 token 为有效的 access_token
http -f POST http://127.0.0.1:8000/o/revoke_token/ token=GJSkO5nRpfyOanF78MOVlcyGScIWNP grant_type=revoke_token client_id=self client_secret=self
# return
HTTP/1.1 200 OK
6. Permissions 权限
6.1 Django-oauth-toolkit 默认提供了以下五个权限类:
-
TokenHasScope: 当 access_token 中的 scope 包含类视图函数中 required_scopes 列表所给范围: 即 时才会允许访问该视图。
-
TokenHasReadWriteScope: 当 access_token 中的 scope 包含类视图中 required_scopes 列表所给范围并且 scope 中含有 'read' 或者 'write': 即 时才允许访问该视图的 GET 请求; 时才允许访问该视图的 POST 请求。
-
TokenHasResourceScope: 即 时才允许访问该视图的 GET 请求; 时才允许访问该视图的 POST 请求。
-
IsAuthenticatedOrTokenHasScope: 当当前请求满足 request.user.is_authenticated 或者 TokenHasScope 权限时允许访问该视图。
-
TokenMatchesOASRequirements: 该权限类根据请求的模式来匹配相应权限规则; 若使用该类则应将类视图中 required_scopes 替换为 required_alternate_scopes,示例如下:
class SongView(views.APIView): authentication_classes = [OAuth2Authentication] permission_classes = [TokenMatchesOASRequirements] required_alternate_scopes = { "GET": [["read"]], "POST": [["create"], ["post", "widget"]], "PUT": [["update"], ["put", "widget"]], "DELETE": [["delete"], ["scope2", "scope3"]], }
6.2 在登录时为 access_token 设置相应权限
在步骤2的配置一步中,
OAUTH2_PROVIDER = {
'SCOPES': {
'read': 'Read scope',
'write': 'Write scope',
'test': 'test api',
'music:write': 'Write music',
'music': 'Test music',
}
}
即为可供资源拥有者(Django User)选择的 当第三方网站调用该网站资源时获取的权限范围。
逻辑则类似为下图中第三方网站将获得以下权限,资源拥有者可以勾选权限。
在获取 access_token 时若不指定 scope 参数,则默认为所有权限范围。
若要指定权限范围则需要传递 scope 参数(以空格隔开):
# 传递 scope 数据(以空格隔开)
http -f POST http://127.0.0.1:8000/o/token/ client_id=self client_secret=self grant_type=password username=admin password=123456 scope="music:write"
7. Signals 信号
Django-oauth-toolkit 提供了 app_authorized 信号,当用户认证时触发该信号。
from oauth2_provider.signals import app_authorized
def handle_app_authorized(sender, request, token, **kwargs):
print('App {} was authorized'.format(token.application.name))
app_authorized.connect(handle_app_authorized)
8. Settings.py 配置
更多默认配置可以查看 oauth_provider/settings.py 文件。
# settings.py
# 指定 oauth_provider 的 models
OAUTH2_PROVIDER_APPLICATION_MODEL = xxx
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = xxx
OAUTH2_PROVIDER_ID_TOKEN_MODEL = xxx
OAUTH2_PROVIDER_GRANT_MODEL = xxx
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = xxx
OAUTH2_PROVIDER = {
'SCOPES': {
'read': 'Read scope',
'write': 'Write scope',
},
'ACCESS_TOKEN_EXPIRE_SECONDS': 60*60*10, # 访问令牌过期时间,默认为36000秒
'ACCESS_TOKEN_GENERATOR': None, # 生成访问令牌的可调用对象的导入路径 默认为None
'ALLOWED_REDIRECT_URI_SCHEMES': ["http", "https"],
'AUTHORIZATION_CODE_EXPIRE_SECONDS': 60, # 建议为10分钟
'REFRESH_TOKEN_EXPIRE_SECONDS': 60*2, # 刷新令牌首次使用与过期之间的秒数,建议为2分钟
'ROTATE_REFRESH_TOKEN': True, # 当客户端刷新访问令牌时,将向客户端发出新的刷新令牌
'REQUEST_APPROVAL_PROMPT': 'auto', # Skip authorization form Can be 'force' or 'auto'
...
}
8. OAuth 认证服务和资源服务分离
8.1 新建另一个 Django 项目:
# 新建 django project
django-admin startproject resource
cd resource
# 创建 users app
python manage.py startapp users
8.2 resource 的配置如下:
# resource/settings.py
INSTALLED_APPS = [
...
'users', # add
'oauth2_provider', # add
'rest_framework', # add
]
OAUTH2_PROVIDER = {
'RESOURCE_SERVER_INTROSPECTION_URL': 'http://127.0.0.1:8000/o/introspect/',
# 'RESOURCE_SERVER_AUTH_TOKEN': 'Lroj8VEAThXttbCiZPxWVlgooVgpGz', # OR this but not both:
'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': ('self', 'self'), # ('client_id', 'client_secret')
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
)
}
8.3 编写 API
将 provider 项目中的 views.py 中的代码复制到 resource 相应位置:
# resource/users/views.py
from django.contrib.auth.models import User
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope
from rest_framework import generics, permissions, serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', "first_name", "last_name")
class UserList(generics.ListCreateAPIView):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
required_scopes = ['test']
# resource/urls.py
from users.views import UserList # add
urlpatterns = [
...
path('users/', UserList.as_view()), # add
]
8.4 同步数据库并访问 API
# 同步数据库
python manage.py migrate
# 启动 resource 服务
python manage.py runserver 0.0.0.0:8001
此时 provider 项目在端口 8000 运行,resource 项目在端口 8001 运行;
使用 provider 返回的 access_token 访问 resource 的 API 接口:
http http://127.0.0.1:8001/users/ Authorization:"Bearer Lroj8VEAThXttbCiZPxWVlgooVgpGz"
8.5 查看 resource admin
为 resource 项目创建一个超级用户(admin2),并登陆 /admin/ 后台查看数据:
可以看到 provider 项目上的 access_token 模型实例以被同步到 resource 中,但是 resource 上并没有 access_token 对应的 refresh_token, 同时 provider 项目上的 access_token 对应的 User 也被同步到 resource 中(未设置密码)。