使用Django實現微信公衆號掃碼登陸非OAuth2.0協議

公司最近做了個論壇使用django開發的,其中用戶登陸部分打算升級爲微信掃碼登陸,調查了一些資料終於實現,現把實現方法貼出來大家一起學習下。微信現在接口現在越來越嚴格了,每出點新功能都要各種驗證,而且接口調用還不固定,現在就一家獨大程序員只能各種忍了。

這次開發沒有采用微信推薦的OAuth2.0協議方式實現微信掃碼登陸,OAuth2.0協議要求比較多,首先你必須是服務號,你的賬號需要註冊並通過微信開放平臺認證,有一個已審覈通過的網站應用,並獲得相應的AppID和AppSecret,申請微信登錄且通過審覈後,可開始接入流程。

微信開放平臺認證的認證非常麻煩,而且已經認證的微信公賬號居然還要認證一次,我只想說中國程序員的工作量就是被這些大公司的不負責造成的。不使用OAuth2.0怎樣實現微信掃碼網頁裏的二維碼後自動登錄呢?

  • 我採取的方式是這樣的,我們可以調用微信公衆號的生成臨時二維碼接口;
  • 獲取一張二維碼把登錄用login_code信息保存到二維碼裏,把這張圖片顯示到網頁裏;
  • 用戶掃碼後會出發一個SCAN事件,這個微信會把這個事件出發的信息發送回我們的服務器;
  • 服務器接收到SCAN事件後取出login_code和用戶唯一id,就可以實現註冊登錄流程了;
  • 另外我們可以用用戶唯一id再發送一個請求去微信服務器獲取用戶詳細信息;
  • 這樣微信掃碼登陸流程就完整了,而且可以增加公衆號的關注一舉兩得。

1.授權流程圖說明

在網頁裏實現微信掃碼登陸,需要準備以下:

2.準備工作

1.一個認證好的微信公衆號

2.微信掃碼登陸步驟

你可能會用到微信測試公衆賬號

3.代碼步驟

1.生成login_code

def createRandomString(len):
    import random
    #print ('wet'.center(10,'*'))
    raw = ""
    range1 = range(58, 65) # between 0~9 and A~Z
    range2 = range(91, 97) # between A~Z and a~z
    i = 0
    while i < len:
        seed = random.randint(48, 122)
        if ((seed in range1) or (seed in range2)):
            continue;
        raw += chr(seed);
        i += 1
    # print(raw)
    return raw

2.使用appid/secret信息獲取tocken

def get_access_token():
    access_token = ''
    try:
        if cache.has_key('access_token') and cache.get('access_token') != '':
            access_token = cache.get('access_token')
            logging.critical('cache access_token:'+access_token)
        else:
            appId = APP_ID
            appSecret = APP_SECRET
            postUrl = ("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s" % (appId, appSecret))
            logging.debug(postUrl)
            urlResp = urllib.urlopen(postUrl).read()
            logging.critical(urlResp)
            urlResp = json.loads(urlResp)
            access_token = urlResp['access_token']
            cache.set('access_token', access_token, 60 * 100)
            #leftTime = urlResp['expires_in']
    except Exception, e:
        logging.critical(e.message)
    return access_token

3.使用tocken獲取ticket

def get_qr_ticket(login_code):
    ticket = ''
    try:
#         if cache.has_key('ticket') and cache.get('ticket') != '':
#             ticket = cache.get('ticket')
#             logging.critical('cache ticket:'+ticket)
#         else:   
        token = get_access_token()
        logging.critical('get_access_token for ticket:'+token)
        data = {
            'expire_seconds': 604800,
            'action_name'   :'QR_STR_SCENE',
            'action_info'   : {
                'scene'     : {
                    'scene_str' : login_code
                }
        }}
        
        import requests as reqs
        params = json.dumps(data)
        #params = urllib.parse.urlencode(data).encode(encoding='UTF8')
        #headers = {'Accept-Charset': 'utf-8', 'Content-Type': 'application/json'}
        if token != '' :
            ticket_url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}".format(token)
            response = reqs.post(url=ticket_url, data=params)
            #response = reqs.urlopen(req).read()
            #get_qr_ticket = urllib.urlopen(ticket_url)
            #urlResp = get_qr_ticket.read().decode("utf-8")
            logging.critical(response.content)
            js_ticket = json.loads(response.content)
            ticket = js_ticket.get("ticket")
            #cache.set('ticket', ticket, 60 * 100)
        #r.setex('wx:ticket', ticket, 7200)
    except Exception as e:
        return ''
    return ticket

4.使用ticket顯示出二維碼圖片

  • https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET 二維碼地址
  • 顯示圖片內嵌 img標籤,<img src=二維碼 /> 並展示在瀏覽器中

5.用戶掃碼

def ajax_signin(request):
    #cache.set('access_token', '',1)
    from django.http import JsonResponse
    login_code = request.GET.get('login_code', None)#User.objects.filter(openid='123qwe')
    #logging.critical('login_code is :' + login_code)
    msg = ''
    next_url=''
    if login_code:
        #search unique user
        try:
            up = UserProfile.objects.get(login_code=login_code)
        except Exception:
            up = None
            
        #logging.critical('up is :' + str(up.pk))
        
        if up :
            user =User.objects.get(pk=up.pk)  
            if user:
                #no passwsod login
                user.backend = 'django.contrib.auth.backends.ModelBackend'
                login(request, user)
                msg = 'success'
                login_redirect_url = getattr(django_settings, 'LOGIN_REDIRECT_URL', None)
                next_url = get_next_url(request, default=login_redirect_url)
                if next_url == reverse('user_signin'):
                    next_url = '%(next)s?next=%(next)s' % {'next': next_url}
            #return HttpResponseRedirect(next_url)
    
    name_dict = {'msg':msg,'next_url':next_url}
    return JsonResponse(name_dict)
  • 掃碼觸發後,會有APP發送消息到微信後臺
  • 微信後臺接收到消息,將轉發到服務端,通過平臺配置的接口配置信息 返回數據
  •  在微信後臺返回數據會包含用戶的openid

         EventKey: session id,對應 步驟1中 login_code 參數信息
         FromUserName:掃碼的用戶openid

5.使用openid獲取用戶信息

def get_wx_userinfo(openid):
    bc_data = {}
    try:
        token = get_access_token()
        logging.critical('get_access_token for ticket:'+token)
        
        import requests as reqs
        if token != '' :
            ticket_url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={}&openid={}&lang=zh_CN".format(token,openid)
            response = reqs.post(url=ticket_url)
            logging.critical('get_wx_userinfo:'+response.content)
            bc_data = json.loads(response.content)
    except Exception as e:
        return ''
    return bc_data

6.保存用戶信息

@csrf_exempt
def weixin_token(request):
    try:
        if request.method == 'GET' and request.GET.get('echostr') != '':
            import hashlib
            wechat_data = request.GET
            signature = wechat_data['signature']
            timestamp = wechat_data['timestamp']
            nonce = wechat_data['nonce']
            logging.info("handle/GET func: hashcode, signature:{0} {1}".format(signature, timestamp))
            echostr = wechat_data['echostr']
            #token = '35977a76fc8a3239867a67a62cf45f0d'
         
            check_list = [APP_TOKEN, timestamp, nonce]
            check_list.sort()
            s1 = hashlib.sha1()
            s1.update(''.join(check_list).encode())
            hashcode = s1.hexdigest()
            logging.debug("handle/GET func: hashcode, signature:{0} {1}".format(hashcode, signature))
            if hashcode == signature:
                return HttpResponse(echostr)
        else:
            postBody = request.body
            logging.critical(postBody)
            import xmltodict
            dictBody = xmltodict.parse(postBody)
            res = WXResponse(dictBody)
            type = res.check_event()
            
            logging.critical('type:'+type)
            
            #scan code
            if type == 'scan' or type == 'unsub_scan':
                openid = res.data.get("FromUserName")
                logging.critical('openid:'+openid)
                
                
                if type == 'unsub_scan':
                    login_code = res.data.get("EventKey").replace('qrscene_','')
                else:
                    login_code = res.data.get("EventKey")
                
                
                logging.critical('login_code:'+login_code)
                #search unique user
                try:
                    up = UserProfile.objects.get(openid=openid)
                except Exception:
                    up = None
                    
                #user =User.objects.get(pk=up.pk)
                # if has user
                if up :
                    UserProfile.objects.filter(openid=openid).update(login_code=login_code)
                else :
                    res = get_wx_userinfo(openid)
                    logging.critical('nickname:'+res['nickname'])
                    user = User()
                    #keep unique username
                    user.username = res['nickname'] + createRandomString(4)
                    user.set_unusable_password()
                    user.first_name = res['nickname']
                    user.last_name = ''
                    user.email = login_code + '@neui.net'
                    user.is_staff = False
                    user.is_superuser = False
                    user.is_active = True
                    user.save()
                    #avatar_urls=res['headimgurl'],
                    logging.critical('user.pk:'+str(user.pk))
                    #upn = UserProfile.objects.filter(pk=user.pk).update(openid = openid,login_code = login_code)
                    #logging.critical('upn:'+str(upn))
                    
                    up_obj = UserProfile.objects.get(pk=user.pk)
                    up_obj.openid = openid
                    up_obj.login_code = login_code
                    up_obj.save()
                    logging.critical('save data')
            
    except Exception, e:
        logging.critical(e.message)
    return HttpResponse("")

 

發佈了83 篇原創文章 · 獲贊 33 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章