django: 微信網(wǎng)頁授權(quán)

微信網(wǎng)頁授權(quán)基礎(chǔ)知識

網(wǎng)頁授權(quán)的最終目的就是獲取微信的用戶信息味悄,微信的網(wǎng)頁授權(quán)方式有兩種

  1. snsapi_base:只能獲取用戶的openid,靜默授權(quán)
  2. snsapi_userinfo:獲取用戶信息扩借,用戶手動同意授權(quán)(這種方式是這篇文章主要講述的)

微信網(wǎng)頁授權(quán)的序列圖

由于沒正式學(xué)過怎么花序列圖,該序列圖肯定是不合規(guī)范的,但整個交互過程還是寫清晰的了。需要明確的是在微信中用戶訪問網(wǎng)頁要經(jīng)過微信鸦难,后端返回?cái)?shù)據(jù)也是要經(jīng)過微信。


網(wǎng)頁授權(quán).png

一些準(zhǔn)備工作

填寫好微信回調(diào)域名员淫,用戶訪問的頁面需要在該域名下合蔽。填錯了,用戶同意授權(quán)后介返,就會出現(xiàn)redirect_uri error辈末。填寫or修改地方在下圖位置


微信回調(diào)域名.pic.jpg

代碼書寫:

urls.py的編寫

#encoding=utf-8
from django.conf.urls import include, url
from .views import AuthView, GetUserInfoView, TestView,  WxSignature

urlpatterns = [
    # 授權(quán)
    url(r'^auth/$', AuthView.as_view(), name='wx_auth'),

    # 獲取用戶信息
    url(r'^code/$', GetUserInfoView.as_view(), name='get_user_info'),

    # 微信接口配置信息驗(yàn)證
    url(r'^$', WxSignature.as_view(), name='signature'),

    # 測試
    url(r'^test/$', TestView.as_view(), name='test_view'),

]

判斷是否已經(jīng)有用戶信息

這里主要利用了View類的實(shí)現(xiàn)機(jī)制,一個url請求過來后映皆,會調(diào)用dispatch()方法挤聘,根據(jù)請求類型選取對應(yīng)的處理方法,例如, GET請求捅彻,就會調(diào)用View類下的get()方法∽槿ィ現(xiàn)在新建一個auth_view.py文件,里面創(chuàng)建一個名為AuthView繼承了View類的class步淹,并改寫dispatch()方法, 判斷的關(guān)鍵是看session中是否存在'user'這個key, 該信息在獲取到了用戶信息后添加到session當(dāng)中从隆,另外請求參數(shù)path保存用戶需要訪問的網(wǎng)頁的url地址。參數(shù)用來保存以后需要進(jìn)行要求的網(wǎng)頁缭裆,繼承AuthView即可進(jìn)行網(wǎng)頁授權(quán)

#encoding=utf-8

import urllib

from django.views.generic import View
from django.http import HttpResponseRedirect
from django.shortcuts import redirect


from django.core.urlresolvers import reverse

import youhui.settings as setting

class AuthView(View):

    def dispatch(self, request, *args, **kwargs):
        # 判斷是否有授權(quán)
        if not 'user' in request.session:
            # 用戶需要訪問的url路徑
            path = request.get_full_path()

            # 跳轉(zhuǎn)url, 
            red_url = '%s?path=%s' % (reverse('wx_auth'), urllib.quote(path))
            return redirect(red_url)

        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

wechat_api.py

該微信api基本就只有網(wǎng)頁授權(quán)部分键闺,我把jeff大牛的wechat sdk, 中有用的部分抽離出來,并把網(wǎng)頁授權(quán)的加了上去澈驼。jeff大牛寫的wechat sdk在這里辛燥。授權(quán)部分在WechatApi類中, 創(chuàng)建時傳入appid和appsecret就可以使用。
主要方法說明:
_process_response: 解析微信返回的json數(shù)據(jù)缝其,返回相對應(yīng)的dict
auth_url: 返回網(wǎng)頁授權(quán)url
get_auth_access_token: 根據(jù)授權(quán)成功返回的code, 獲取網(wǎng)頁授權(quán)的access_token
get_user_info: 根據(jù)網(wǎng)頁授權(quán)的access_token獲取用戶信息

#encoding=utf-8

import requests
import simplejson
import urllib
import logging

log = logging.getLogger('django')

class APIError(object):
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg

def wx_log_error(APIError):
    log.error('wechat api error: [%s], %s' % (APIError.code, APIError.msg))

class WechatBaseApi(object):

    API_PREFIX = u'https://api.weixin.qq.com/cgi-bin/'

    def __init__(self, appid, appsecret, api_entry=None):
        self.appid = appid
        self.appsecret = appsecret
        self._access_token = None
        self.api_entry = api_entry or self.API_PREFIX

    @property
    def access_token(self):
        if not self._access_token:
            token, err = self.get_access_token()

            if not err:
                self._access_token = token['access_token']
                return self._access_token
            else:
                return None

        return self._access_token


    # 解析微信返回的json數(shù)據(jù)挎塌,返回相對應(yīng)的dict
    def _process_response(self, rsp):
        if 200 != rsp.status_code:
            return None, APIError(rsp.status_code, 'http error')
        try:
            content = rsp.json()

        except Exception:
            return None, APIError(9999, 'invalid response')
        if 'errcode' in content and content['errcode'] != 0:
            return None, APIError(content['errcode'], content['errmsg'])

        return content, None


    def _get(self, path, params=None):
        if not params:
            params = {}

        params['access_token'] = self.access_token

        rsp = requests(self.api_entry + path, params=params)

        return self._process_response(rsp)


    def _post(self, path, data, type='json'):

        header = {'content-type': 'application/json'}

        if '?' in path:
            url = self.api_entry + path + 'access_token=' + self.access_token
        else:
            url = self.api_entry + path + '?' + 'access_token=' + self.access_token

        if 'json' == type:
            data = simplejson.dumps(data, ensure_ascii=False).encode('utf-8')

        rsp = requests.post(url, data, headers=header)

        return self._process_response(rsp)



class WechatApi(WechatBaseApi):

    def get_access_token(self, url=None, **kwargs):
        params = {'grant_type': 'client_credential', 'appid': self.appid, 'secret': self.appsecret}

        if kwargs:
            params.update(kwargs)

        rsp = requests.get(url or self.api_entry + 'token', params)

        return self._process_response(rsp)
    
    #返回授權(quán)url
    def auth_url(self, redirect_uri, scope='snsapi_userinfo', state=None):
        url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect' % \
              (self.appid, urllib.quote(redirect_uri), scope, state if state else '')
        return url
    
   # 獲取網(wǎng)頁授權(quán)的access_token
    def get_auth_access_token(self, code):
        url = u'https://api.weixin.qq.com/sns/oauth2/access_token'
        params = {
            'appid': self.appid,
            'secret': self.appsecret,
            'code': code,
            'grant_type': 'authorization_code'
        }

        return self._process_response(requests.get(url, params=params))
    
    # 獲取用戶信息
    def get_user_info(self, auth_access_token, openid):
        url = u'https://api.weixin.qq.com/sns/userinfo?'
        params = {
            'access_token': auth_access_token,
            'openid': openid,
            'lang': 'zh_CN'
        }

        return self._process_response(requests.get(url, params=params))

view.py

這部分代碼實(shí)現(xiàn)了上面授權(quán)序列圖的步驟6到18,需要注意的是需要設(shè)置user信息到session中内边,當(dāng)然也可以設(shè)置其他信息榴都,在AuthView中判斷條件要與之相對應(yīng)即可。

#encoding=utf-8

import hashlib
import simplejson
from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseServerError, Http404
from django.views.generic import View
from django.core.urlresolvers import reverse

from .models import User
from .serializers import UserSerializer
from .auth_view import AuthView as BaseView
from .wechat_api import WechatApi, wx_log_error
import youhui.settings as settings
from youhui.utils import log_err

# Create your views here.



class WecahtApiView(View):

    # 填入公眾號appid, appsecret
    APPID = settings.APPID
    APPSECRET = settings.APPSECRET
    HOST = settings.HOST

    wechat_api = WechatApi(appid=APPID, appsecret=APPSECRET)


class WxSignature(View):
      pass #非重點(diǎn)省略

class AuthView(WecahtApiView):
    def get(self, request):

        path = request.GET.get('path')
        if path:
            if 'user' in request.session:
                return redirect(path)
            else:
                red_url = '%s%s?path=%s' % (self.HOST, reverse('wx:get_user_info'), path)
                redirect_url = self.wechat_api.auth_url(red_url)

                print 'auth_url', redirect_url
                return redirect(redirect_url)
        else:
            return Http404('parameter path not founded!')



class GetUserInfoView(WecahtApiView):
    def get(self, request):

    
        redir_url = request.GET.get('path')
        code = request.GET.get('code')

        if redir_url and code:

            # 獲取網(wǎng)頁授權(quán)access_token
            token_data, error = self.wechat_api.get_auth_access_token(code)

            if error:
                wx_log_error(error)
                return HttpResponseServerError('get access_token error')

            # 獲取用戶信息信息
            user_info, error = self.wechat_api.get_user_info(token_data['access_token'], token_data['openid'])

            if error:
                wx_log_error(error)
                return HttpResponseServerError('get userinfo error')

            # 存儲用戶信息
            user = self._save_user(user_info)
            if not user:
                return HttpResponseServerError('save userinfo error')

            # 用戶對象存入session
            request.session['user'] = user

            # 跳轉(zhuǎn)回目標(biāo)頁面
            return redirect(redir_url)

        # 用戶禁止授權(quán)后怎么操作
        else:
            return Http404('parameter path or code not founded!!')

    def _save_user(self, data):
        user = User.objects.filter(openid=data['openid'])

        # 沒有則存儲用戶數(shù)據(jù)漠其,有返回用戶數(shù)據(jù)的字典
        if 0 == user.count():
            user_data = {
                'nick': data['nickname'].encode('iso8859-1').decode('utf-8'),
                'openid': data['openid'],
                'avatar': data['headimgurl'],
                'info': self._user2utf8(data),
            }

            if 'unionid' in data:
                user_data.update('unionid', data.unionid)

            try:
                new_user = User(**user_data)
                new_user.save()

                user_data.update({'id': new_user.id})

                return user_data
            except Exception, e:
                log_err(e)

            return None
        else:
            # 把User對象序列化成字典嘴高,具體看rest_framework中得內(nèi)容
            return UserSerializer(user[0]).data


    # 解決中文顯示亂碼問題
    def _user2utf8(self, user_dict):
        utf8_user_info = {
            "openid": user_dict['openid'],
            "nickname": user_dict['nickname'].encode('iso8859-1').decode('utf-8'),
            "sex": user_dict['sex'],
            "province": user_dict['province'].encode('iso8859-1').decode('utf-8'),
            "city": user_dict['city'].encode('iso8859-1').decode('utf-8'),
            "country": user_dict['country'].encode('iso8859-1').decode('utf-8'),
            "headimgurl": user_dict['headimgurl'],
            "privilege": user_dict['privilege'],
        }

        if 'unionid' in user_dict:
            utf8_user_info.update({'unionid': user_dict['unionid']})

        return utf8_user_info


class TestView(BaseView):
    def get(self, request):
        
        return render(request, 'test.html')


參考

微信網(wǎng)頁授權(quán)文檔
微信python api

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竿音,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拴驮,更是在濱河造成了極大的恐慌谍失,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莹汤,死亡現(xiàn)場離奇詭異快鱼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纲岭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門抹竹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人止潮,你說我怎么就攤上這事窃判。” “怎么了喇闸?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵袄琳,是天一觀的道長。 經(jīng)常有香客問我燃乍,道長唆樊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任刻蟹,我火速辦了婚禮逗旁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舆瘪。我一直安慰自己片效,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布英古。 她就那樣靜靜地躺著淀衣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪召调。 梳的紋絲不亂的頭發(fā)上膨桥,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音某残,去河邊找鬼国撵。 笑死陵吸,一個胖子當(dāng)著我的面吹牛玻墅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壮虫,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼澳厢,長吁一口氣:“原來是場噩夢啊……” “哼环础!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起剩拢,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤线得,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徐伐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贯钩,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年办素,在試婚紗的時候發(fā)現(xiàn)自己被綠了角雷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡性穿,死狀恐怖勺三,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情需曾,我是刑警寧澤吗坚,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站呆万,受9級特大地震影響商源,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谋减,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一炊汹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逃顶,春花似錦讨便、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盈蛮,卻和暖如春废菱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抖誉。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工殊轴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人袒炉。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓旁理,卻偏偏與公主長得像,于是被迫代替她去往敵國和親我磁。 傳聞我的和親對象是個殘疾皇子孽文,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容