笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。
1. 在Django中集成Redis
Redis 为内存数据库,目前我们使用的是 Django 自带的数据库 SQLite,且能够很容易地迁移到 MySQL,这些数据库的效率不如 Redis,其特点为:
- Redis 存的内容为
key, value
对,而其它数据库存的是若干张表,每张表为若干条目; - Redis 为单线程的,不会出现读写冲突。
首先我们需要先安装 Redis:
sudo apt-get update
sudo apt-get install redis-server
sudo service redis-server status # 查看redis-server状态
pip install django_redis
接着配置一下 Django 的缓存机制,将下面这段代码复制到 settings.py
中:
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
USER_AGENTS_CACHE = 'default'
然后启动 redis-server
,启动后可以使用 top
命令查看 redis-server
是否运行:
sudo redis-server /etc/redis/redis.conf
此时在项目根目录执行 python3 manage.py shell
进入 IPython 交互界面,输入以下代码测试一下 Redis:
In [1]: from django.core.cache import cache
In [2]: cache.keys('*') # 查找当前数据库的所有关键字,支持正则匹配
Out[2]: []
In [3]: cache.set('yyj', 1, 5) # 设置关键字,第三个参数表示多长时间过期,单位是秒,None表示不会过期
Out[3]: True
In [4]: cache.set('abc', 2, None)
Out[4]: True
In [5]: cache.has_key('abc') # 查询某个键是否存在
Out[5]: True
In [6]: cache.has_key('yyj')
Out[6]: False
In [7]: cache.get('abc') # 查询某个键的值
Out[7]: 2
In [8]: cache.delete('abc') # 删除某个关键字
Out[8]: True
注意如果出现报错:ConnectionError: Error 111 connecting to 127.0.0.1:6379. Connection refused.
说明 redis-server
没有启动。
2. 申请授权码
一键授权登录的流程是用户点击一键登录后弹出确认授权的页面,用户确认后就自动创建一个新的账号,且登录到网站里。
具体交互流程如下图所示,用户点击按钮后向网站服务器端(Web)发起申请,请求用 AcWing 账号登录,然后 Web 将自己的 AppID
报给 AcWing,AcWing 给用户返回一个页面询问用户是否要授权给刚刚的网站,如果用户同意,那么 AcWing 会将一个授权码 code
(两小时有效期)发给 Web,Web 接到授权码后再加上自己的身份信息 AppSecret
以及 AppID
向 AcWing 申请一个授权令牌 access-token
(两小时有效期)和用户的 openid
(唯一辨别用户),Web 拿到令牌和用户 ID 后即可向 AcWing 申请用户的用户名和头像。
由于我们需要记录每个用户的 openid
,因此我们需要在数据库(game/models/player/
)的 Player
类中添加一个信息:
from django.db import models
from django.contrib.auth.models import User
# Player有两个关键字,user表示是和哪个User对应的,avatar表示头像
class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # 当删除User时也将其关联的Player一块删掉
avatar = models.URLField(max_length=256, blank=True) # 头像用链接存
openid = models.CharField(default='', max_length=50, blank=True, null=True)
def __str__(self): # 显示每个Player的数据
return str(self.user)
在根目录下更新一下数据库:
python3 manage.py makemigrations
python3 manage.py migrate
申请授权码的请求地址:https://www.acwing.com/third_party/api/oauth2/web/authorize/
。
请求方法为 GET
,参考示例:
https://www.acwing.com/third_party/api/oauth2/web/authorize/?appid=APPID&redirect_uri=REDIRECT_URI&scope=SCOPE&state=STATE
参数说明:
-
appid
:应用的唯一 ID,可以在 AcWing 编辑 AcApp 的界面里看到; -
redirect_uri
:接收授权码的地址,表示 AcWing 端要将授权码返回到哪个链接,需要对链接进行编码:Python3 中使用urllib.parse.quote
;Java 中使用URLEncoder.encode
; -
scope
:申请授权的范围,目前只需填userinfo
; -
state
:用于判断请求和回调的一致性,授权成功后原样返回该参数值,即接收授权码的地址需要判断是否是 AcWing 发来的请求(判断收到的state
与发送出去的state
是否相同),如果不是直接 Pass。该参数可用于防止 CSRF 攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数(如果是将第三方授权登录绑定到现有账号上,那么推荐用随机数 + user_id
作为state
的值,可以有效防止 CSRF 攻击)。此处state
可以存到 Redis 中,设置两小时有效期。
用户同意授权后会重定向到 redirect_uri
,返回参数为 code
和 state
。链接格式如下:
redirect_uri?code=CODE&state=STATE
如果用户拒绝授权,则不会发生重定向。
我们在 game/views/settings
目录中创建一个 acwing
目录表示 AcWing 授权登录,然后在该目录中先创建一个 __init__.py
,再创建两个子目录 web
和 acapp
分别表示 Web 端的 AcWing 一键登录以及 AcApp 端的 AcWing 一键登录,最后同样在这两个子目录中创建 __init__.py
文件。
在 web
目录下创建申请授权码的 API apply_code.py
:
from django.http import JsonResponse
def apply_code(request):
appid = '4007'
return JsonResponse({
'result': 'success',
})
接着创建接收授权码的 API receive_code.py
:
from django.shortcuts import redirect
def receive_code(request):
return redirect('index') # 重定向回index
然后在 game/urls/settings
目录中也创建一个 acwing
目录,在该目录中创建 __init__.py
和 index.py
,index.py
内容如下:
from django.urls import path
from game.views.settings.acwing.web.apply_code import apply_code
from game.views.settings.acwing.web.receive_code import receive_code
urlpatterns = [
path('web/apply_code/', apply_code, name='settings_acwing_web_apply_code'),
path('web/receive_code/', receive_code, name='settings_acwing_web_receive_code'),
]
然后需要将该目录 include
进 settings
目录的 index.py
中:
from django.urls import path, include
from game.views.settings.getinfo import getinfo
from game.views.settings.login import mylogin
from game.views.settings.logout import mylogout
from game.views.settings.register import register
urlpatterns = [
path('getinfo/', getinfo, name='settings_getinfo'),
path('login/', mylogin, name='settings_login'),
path('logout/', mylogout, name='settings_logout'),
path('register/', register, name='settings_register'),
path('acwing/', include('game.urls.settings.acwing.index')),
]
重启项目后即可访问 https://<公网IP>/settings/acwing/web/apply_code/
以及 https://<公网IP>/settings/acwing/web/receive_code/
测试效果。
现在来看看 urllib.parse.quote
的作用,它能够将链接重新编码,替换掉原本的部分特殊字符防止出现 BUG:
In [1]: from urllib.parse import quote
In [2]: url = 'https://app4007.acapp.acwing.com.cn/settings/acwing/web/receive_code/?args=yyj'
In [3]: quote(url)
Out[3]: 'https%3A//app4007.acapp.acwing.com.cn/settings/acwing/web/receive_code/%3Fargs%3Dyyj'
现在我们完善一下 apply_code.py
的内容:
from django.http import JsonResponse
from django.core.cache import cache
from urllib.parse import quote
from random import randint
def get_state(): # 获得8位长度的随机数
res = ''
for i in range(8):
res += str(randint(0, 9))
return res
def apply_code(request):
appid = '4007'
redirect_uri = quote('https://app4007.acapp.acwing.com.cn/settings/acwing/web/receive_code/')
scope = 'userinfo'
state = get_state()
cache.set(state, True, 7200) # 有效期2小时
apply_code_url = 'https://www.acwing.com/third_party/api/oauth2/web/authorize/'
return JsonResponse({
'result': 'success',
'apply_code_url': apply_code_url + '?appid=%s&redirect_uri=%s&scope=%s&state=%s' % (appid, redirect_uri, scope, state)
})
然后修改一下前端代码(Settings
类):
class Settings {
constructor(root) {
...
this.$acwingoption = this.$login.find('.ac_game_settings_acwingoption img');
...
}
start() { // 在初始化时需要从服务器端获取用户信息
...
}
add_listening_events() { // 绑定监听函数
this.add_listening_events_login();
this.add_listening_events_register();
}
add_listening_events_login() {
let outer = this;
this.$login_register.click(function() {
outer.register();
});
this.$login_submit.click(function() {
outer.login_on_remote();
});
this.$acwingoption.click(function() {
outer.acwing_login();
});
}
add_listening_events_register() {
...
}
login_on_remote() { // 在远程服务器上登录
...
}
register_on_remote() { // 在远程服务器上注册
...
}
logout_on_remote() { // 在远程服务器上登出
...
}
acwing_login() {
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/acwing/web/apply_code/',
type: 'GET',
success: function(resp) {
console.log(resp);
if (resp.result === 'success') {
window.location.replace(resp.apply_code_url); // 将当前页面重定向
}
}
})
}
register() { // 打开注册界面
...
}
login() { // 打开登录界面
...
}
getinfo() {
...
}
hide() {
...
}
show() {
...
}
}
3. 申请授权令牌和用户ID
请求地址:https://www.acwing.com/third_party/api/oauth2/access_token/
。
请求方法为 GET
,参考示例:
https://www.acwing.com/third_party/api/oauth2/access_token/?appid=APPID&secret=APPSECRET&code=CODE
参数说明:
-
appid
:应用的唯一 ID,可以在 AcWing 编辑 AcApp 的界面里看到; -
secret
:应用的秘钥,可以在 AcWing 编辑 AcApp 的界面里看到; -
code
:上一步中获取的授权码。
申请成功的返回内容示例:
{
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"openid": "OPENID",
"scope": "SCOPE",
}
申请失败的返回内容示例:
{
"errcode": 40001,
"errmsg": "code expired", # 授权码过期
}
返回参数说明:
-
access_token
:授权令牌,有效期2小时; -
expires_in
:授权令牌还有多久过期,单位(秒); -
refresh_token
:用于刷新access_token
的令牌,有效期30天; -
openid
:用户的 ID。每个 AcWing 用户在每个 AcApp 中授权的openid
是唯一的,可用于识别用户; -
scope
:用户授权的范围。目前范围为userinfo
,包括用户名、头像。
现在我们点击 AcWing 一键登录按钮即可跳出请求授权的页面,AcWing 会向 receive_code
函数发送 code
以及 state
,现在我们完善 receive_code.py
接到授权后的操作:
from django.shortcuts import redirect
from django.core.cache import cache
import requests
def receive_code(request):
data = request.GET
code = data.get('code')
state = data.get('state')
if not cache.has_key(state):
return redirect('index')
cache.delete(state)
apply_access_token_url = 'https://www.acwing.com/third_party/api/oauth2/access_token/'
params = {
'appid': '4007',
'secret': '0edf233ee876407ea3542220e2a8d83e',
'code': code
}
access_token_res = requests.get(apply_access_token_url, params=params).json() # 申请授权令牌
print(access_token_res)
return redirect('index') # 重定向回index
现在我们点击一键登录按钮即可在后台看到接收到的授权令牌内容。
4. 申请用户信息
请求地址:https://www.acwing.com/third_party/api/meta/identity/getinfo/
。
请求方法为 GET
,参考示例:
https://www.acwing.com/third_party/api/meta/identity/getinfo/?access_token=ACCESS_TOKEN&openid=OPENID
参数说明:
-
access_token
:上一步中获取的授权令牌; -
openid
:上一步中获取的用户openid
。
申请成功的返回内容示例:
{
'username': "USERNAME",
'photo': "https:cdn.acwing.com/xxxxx"
}
申请失败的返回内容示例:
{
'errcode': "40004",
'errmsg': "access_token expired" # 授权令牌过期
}
现在我们用授权令牌向 AcWing 申请用户的用户名和头像,进一步完善 receive_code.py
:文章来源:https://www.toymoban.com/news/detail-507484.html
from django.shortcuts import redirect
from django.core.cache import cache
from django.contrib.auth.models import User
from django.contrib.auth import login
from game.models.player.player import Player
from random import randint
import requests
def receive_code(request):
data = request.GET
code = data.get('code')
state = data.get('state')
if not cache.has_key(state):
return redirect('index')
cache.delete(state)
apply_access_token_url = 'https://www.acwing.com/third_party/api/oauth2/access_token/'
params = {
'appid': '4007',
'secret': '0edf233ee876407ea3542220e2a8d83e',
'code': code
}
access_token_res = requests.get(apply_access_token_url, params=params).json() # 申请授权令牌
access_token = access_token_res['access_token']
openid = access_token_res['openid']
player = Player.objects.filter(openid=openid) # 查看当前用户的openid是否已经存在
if player.exists():
login(request, player[0].user) # 如果用户的账号已经存在直接登录即可
return redirect('index')
get_userinfo_url = 'https://www.acwing.com/third_party/api/meta/identity/getinfo/'
params = {
'access_token': access_token,
'openid': openid
}
get_userinfo_res = requests.get(get_userinfo_url, params=params).json() # 申请获取用户信息
username = get_userinfo_res['username']
avatar = get_userinfo_res['photo']
while User.objects.filter(username=username).exists(): # 如果当前用户的用户名已经存在则在其后面添加若干位随机数
username += str(randint(0, 9))
user = User.objects.create(username=username) # 创建该用户,没有密码
player = Player.objects.create(user=user, avatar=avatar, openid=openid)
login(request, user) # 创建好后直接登录
return redirect('index') # 重定向回index
至此 Web 端的 AcWing 一键登录已经实现。文章来源地址https://www.toymoban.com/news/detail-507484.html
到了这里,关于Django学习笔记-Web端授权AcWing一键登录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!