本节内容是通过 OAuth2 实现 AcApp 端的 AcWing 一键登录功能。 
AcApp 端使用 AcWing 一键授权登录的流程与之前网页端的流程一样,只有申请授权码这一步有一点细微的差别。
 
 
我们在打开 AcApp 应用之后会自动向 AcWing 请求账号登录,客户端会向后端服务器请求一些参数,然后后端服务器向 AcWing 请求授权码,然后 AcWing 在接到请求之后会询问用户是否要授权登录,如果用户同意了那么 AcWing 会给客户端发送一个授权码,客户端可以通过授权码加上自己的身份信息向 AcWing 服务器请求自己的授权令牌 access_token 和用户的 openid,最后客户端在拿到令牌和 ID 后即可向 AcWing 服务器请求用户的用户名和头像等信息。
在网页端授权登录时我们使用的方法是通过 URL 的方式重定向到某一个链接里申请授权码,而这次的 AcApp 不是通过链接,而是通过 AcWing 的一个 API 申请,请求授权码的 API:
1 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。
如果用户拒绝授权,则将会收到如下错误码:
1 2 3 4 {     errcode : "40010"      errmsg : "user reject"  } 
 
我们在 game/views/settings/acwing/acapp 目录中将之前网页端的 apply_code.py 与 receive_code.py 复制过来,然后对 apply_code.py 进行一点小修改,这次不是返回一个链接,而是返回四个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from  django.http import  JsonResponsefrom  django.core.cache import  cachefrom  urllib.parse import  quotefrom  random import  randintdef  get_state ():      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/acapp/receive_code/' )     scope = 'userinfo'      state = get_state()     cache.set (state, True , 7200 )            return  JsonResponse({         'result' : 'success' ,         'appid' : appid,         'redirect_uri' : redirect_uri,         'scope' : scope,         'state' : state,     }) 
 
进入 game/urls/settings/acwing 修改一下路由:
1 2 3 4 5 6 7 8 9 10 11 12 from  django.urls import  pathfrom  game.views.settings.acwing.web.apply_code import  apply_code as  web_apply_codefrom  game.views.settings.acwing.web.receive_code import  receive_code as  web_receive_codefrom  game.views.settings.acwing.acapp.apply_code import  apply_code as  acapp_apply_codefrom  game.views.settings.acwing.acapp.receive_code import  receive_code as  acapp_receive_codeurlpatterns = [     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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 from  django.http import  JsonResponsefrom  django.core.cache import  cachefrom  django.contrib.auth.models import  Userfrom  game.models.player.player import  Playerfrom  random import  randintimport  requestsdef  receive_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' )     if  not  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)       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 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 class  Settings  {    constructor (root ) {         this .root  = root;         this .platform  = '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 ( ) {           let  outer = this ;         $.ajax ({             url : 'https://app4007.acapp.acwing.com.cn/settings/getinfo/' ,                            type : 'GET' ,             data : {                 platform : outer.platform ,             },             success : function (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 ;                  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 启动后的报错内容修改代码。
上一章:Django学习笔记-VS Code本地运行项目 。
下一章:Django学习笔记-实现联机对战(上) 。