公司最近做了個論壇使用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("")