笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。
AcApp 端使用 AcWing 一键授权登录的流程与之前网页端的流程一样,只有申请授权码这一步有一点细微的差别:
我们在打开 AcApp 应用之后会自动向 AcWing 请求账号登录,客户端会向后端服务器请求一些参数,然后后端服务器向 AcWing 请求授权码,然后 AcWing 在接到请求之后会询问用户是否要授权登录,如果用户同意了那么 AcWing 会给客户端发送一个授权码,客户端可以通过授权码加上自己的身份信息向 AcWing 服务器请求自己的授权令牌
access_token
和用户的
openid
,最后客户端在拿到令牌和 ID 后即可向 AcWing 服务器请求用户的用户名和头像等信息。
在网页端授权登录时我们使用的方法是通过 URL 的方式重定向到某一个链接里申请授权码,而这次的 AcApp 不是通过链接,而是通过 AcWing 的一个 API 申请,请求授权码的 API:
AcWingOS.api.oauth2.authorize(appid, redirect_uri, scope, state, callback);
参数说明:
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 中,设置两小时有效期;callback
:redirect_uri
返回后的回调函数,即接受receive_code
函数向前端返回的信息。
用户同意授权后,会将
code
和
state
传递给
redirect_uri
。
如果用户拒绝授权,则将会收到如下错误码:
{errcode:"40010"errmsg:"user reject"}
我们在
game/views/settings/acwing/acapp
目录中将之前网页端的
apply_code.py
与
receive_code.py
复制过来,然后对
apply_code.py
进行一点小修改,这次不是返回一个链接,而是返回四个参数:
from django.http import JsonResponse
from django.core.cache import cache
from urllib.parse import quote
from random import randint
defget_state():# 获得8位长度的随机数
res =''for i inrange(8):
res +=str(randint(0,9))return res
defapply_code(request):
appid ='4007'
redirect_uri = quote('https://app4007.acapp.acwing.com.cn/settings/acwing/acapp/receive_code/')
scope ='userinfo'
state = get_state()
cache.set(state,True,7200)# 有效期2小时# 需要返回四个参数return JsonResponse({'result':'success','appid': appid,'redirect_uri': redirect_uri,'scope': scope,'state': state,})
进入
game/urls/settings/acwing
修改一下路由:
from django.urls import path
from game.views.settings.acwing.web.apply_code import apply_code as web_apply_code
from game.views.settings.acwing.web.receive_code import receive_code as web_receive_code
from game.views.settings.acwing.acapp.apply_code import apply_code as acapp_apply_code
from game.views.settings.acwing.acapp.receive_code import receive_code as acapp_receive_code
urlpatterns =[
path('web/apply_code/', web_apply_code, name='settings_acwing_web_apply_code'),
path('web/receive_code/', web_receive_code, name='settings_acwing_web_receive_code'),
path('acapp/apply_code/', acapp_apply_code, name='settings_acwing_acapp_apply_code'),
path('acapp/receive_code/', acapp_receive_code, name='settings_acwing_acapp_receive_code'),]
现在访问
https://app4007.acapp.acwing.com.cn/settings/acwing/acapp/apply_code/
即可看到返回内容。
然后我们修改一下
receive_code.py
:
from django.http import JsonResponse
from django.core.cache import cache
from django.contrib.auth.models import User
from game.models.player.player import Player
from random import randint
import requests
defreceive_code(request):
data = request.GET
if'errcode'in data:return JsonResponse({'result':'apply failed','errcode': data['errcode'],'errmsg': data['errmsg'],})
code = data.get('code')
state = data.get('state')ifnot cache.has_key(state):return JsonResponse({'result':'state not exist',})
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']
players = Player.objects.filter(openid=openid)# filter不管存不存在都会返回一个列表,get如果不存在会报异常if players.exists():# 用户如果已存在就直接返回用户
player = players[0]return JsonResponse({'result':'success','username': player.user.username,'avatar': player.avatar,})
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)return JsonResponse({'result':'success','username': player.user.username,'avatar': player.avatar,})
接着我们修改前端文件,也就是
game/static/js/src/settings
目录中的
Settings
类:
classSettings{constructor(root){this.root = root;this.platform ='WEB';// 默认为Web前端if(this.root.acwingos)this.platform ='ACAPP';this.username ='';// 初始用户信息为空this.avatar ='';this.$settings =$(`
...
`);...this.start();}start(){// 在初始化时需要从服务器端获取用户信息if(this.platform ==='WEB'){this.getinfo_web();this.add_listening_events();}else{this.getinfo_acapp();}}add_listening_events(){// 绑定监听函数...}add_listening_events_login(){...}add_listening_events_register(){...}login_on_remote(){// 在远程服务器上登录...}register_on_remote(){// 在远程服务器上注册...}acwing_login(){...}register(){// 打开注册界面...}login(){// 打开登录界面...}getinfo_web(){// 此处将之前的getinfo函数名进行了修改用来区分let outer =this;
$.ajax({url:'https://app4007.acapp.acwing.com.cn/settings/getinfo/',// 用AcWing部署// url: 'http://8.130.54.44:8000/settings/getinfo/', // 用云服务器部署type:'GET',data:{platform: outer.platform,},success:function(resp){// 调用成功的回调函数,返回的Json字典会传给resp
console.log(resp);// 控制台输出查看结果if(resp.result ==='success'){
outer.username = resp.username;
outer.avatar = resp.avatar;
outer.hide();
outer.root.menu.show();}else{// 如果未登录则需要弹出登录界面
outer.login();}}});}acapp_login(appid, redirect_uri, scope, state){let outer =this;// resp是redirect_uri的返回值,此处为用户名和头像this.root.acwingos.api.oauth2.authorize(appid, redirect_uri, scope, state,function(resp){
console.log(resp);if(resp.result ==='success'){
outer.username = resp.username;
outer.avatar = resp.avatar;
outer.hide();
outer.root.menu.show();}});}getinfo_acapp(){let outer =this;
$.ajax({url:'https://app4007.acapp.acwing.com.cn/settings/acwing/acapp/apply_code/',type:'GET',success:function(resp){if(resp.result ==='success'){
outer.acapp_login(resp.appid, resp.redirect_uri, resp.scope, resp.state);}}});}hide(){this.$settings.hide();}show(){this.$settings.show();}}
注意,如果遇到跨域问题:
Access to XMLHttpRequest at 'XXX'
,大概率是某个文件的内容写错了,可以检查 uWSGI 启动后的报错内容修改代码。
版权归原作者 柃歌 所有, 如有侵权,请联系我们删除。