再谈微信公众号网页授权的40163错误

{"errcode":40163,"errmsg":"code been used}

进来看这篇文章的你,肯定在微信公众号开发的时候看到过这行代码吧,没错,通过点击微信公众号菜单或者其他方式做oauth认证(比如微信支付估计也会要用到),都会要通过oauth认证拿到访问者的openid,以及user_info,这个code只能用一次,但是在安卓手机上,点击一次会产生两次访问,导致获取access_token失败!

2017年7月,我遇到了这个问题,当时在SegmentFault  https://segmentfault.com/q/1010000010308461 里提了问,后来又跟踪了两天,也没彻底搞懂,反正时好时坏,,后来自己主要精力放在H5+开发上,就不怎么关心公众号这块了;

2018年7月,我又带着python重回公众号,又遇到了这个问题,这次绕不过去了,一年过去了,网上搜索一下,问这个问题的人还是很多,而且也没有真正解决这个问题,

比如这条:https://developers.weixin.qq.com/blogdetail?action=get_post_info&lang=zh_CN&token=&docid=b8f9f09573e92ffb0e23308d54bcdcf7

再比如:https://blog.csdn.net/hejisan/article/details/80374113

其实这个问题也不复杂,总结起来说,就是“点击公众号菜单产生两次请求”,导致code被用了两次,但是因为微信官方的文档没有相关内容,大家也都用自己的方式解决,我的解答应该也只是其中一种吧。

有人说:“后来发现只要加个属性就不会有这个问题了。

https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect

&connect_redirect=1 这个参数”

!很可惜,这条加上去,对我一点也不起作用!

又仔细阅读微信官方的文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
这里有
appid
redirect_uri
response_type
scope
state
wechat_redirect
的作用说明,但是并没有上面提到的connect_redirect,

提到了一个state的参数,这个是干嘛用的呢?
/////////////////////////////
state 否 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
/////////////////////////////

测试的时候发现,在访问https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect的时候,无论state填什么,获取code的时候,这个字段会原样返回过来,好就用它来解决。

既然这个参数会被原样返回过来,那我就利用它作为一个令牌,发送之前先生成一个随机数,然后保存到一个全局变量里,在redirect_uri页面的接收到这个随机数,跟全局变量对比一下,如果一样,就把这个令牌改掉,这样第二次访问的时候,这个令牌就失效了,就pass掉这个请求,避免了被2次访问的情况。

PS.
纯php没有全局变量,用一些框架应该就会有,或者写到session或者缓存都行;
我是在django里开发的,所以就直接搞了个全局变量,简单粗暴。
我是python初学者,代码写得比较拖沓啰嗦,见谅。
有人说是微信内置浏览器的问题,也有人说是nginx的问题,我觉得应该不是nginx,因为这次用python用的是django内置的服务器,也同样情况。这个问题在苹果手机上好像就没有,主要是在安卓版微信上有,因为自己已经不用苹果手机了,所以也没有做测试。

最后,上代码:

我的开发环境:
python 3.5.3
django 2.0.7

view.py代码如下:

import json
import random
import requests




#全局变量
APP_ID          = '公众号ID'
App_SEC         = '公众号SEC'
RADOM_STATE     = ""



def oauth(request):

    global RADOM_STATE

    _code = request.GET

    #1 第一步,拿到code
    oauth_state = _code['state']
    oauth_code = _code['code']
    oauth_info_json = {
        'oauth_state' : oauth_state,
        'oauth_code' : oauth_code
    }
    #print("=====>" + json.dumps(oauth_info_json))


    print(oauth_state)    #本次拿到的随机数令牌
    print(RADOM_STATE)    #全局变量里保存的跳转之前生成的随机数令牌

    #比较本次state和全局随机数令牌是否一致,不一致,说明是第二次访问,丢弃
    if RADOM_STATE != oauth_state:
        pass
    else:
        #一致,重新生成随机数令牌,防止第二次访问造成code失效
        RADOM_STATE = str(random.randint(1000, 9999))
        # print(RADOM_STATE)

        # 2 第二步, 换取access_token
        url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code'.format(APP_ID, App_SEC, oauth_code)
        print(url)
        oauth_access_token = requests.get(url)
        print("=====>" + oauth_access_token.text)
        re_json_str = oauth_access_token.text
        # print(type(re_json_str), re_json_str)
        re_json = json.loads(re_json_str)
        # print(type(re_json), re_json)



        # 3 第三步,拿到用户信息
        try:
            url = 'https://api.weixin.qq.com/sns/userinfo?access_token={}&openid={}&lang=zh_CN'.format(re_json['access_token'], re_json['openid'])
            print(url)
            user_info = requests.get(url)
            #print(user_info.encoding)
            #print(user_info.text.encode("iso-8859-1").decode('utf8'))

            user_info_json_str = user_info.text.encode("iso-8859-1").decode('utf8')            #微信传过来的中文使用的iso8859编码,为了在模板里能显示出来,要转换成utf8编码
            # print(type(user_info_json_str), user_info_json_str)

            user_info_json = json.loads(user_info_json_str)
            # print(type(user_info_json), user_info_json)

        except Exception as e:
            print(e)



        content = {
            'oauth_info': oauth_info_json,  #oauth_info_json
            'user_info': user_info_json     #user_info_json
        }
        return render(request, 'user_info.html', content)    #把数据给html模板,其实oauth_info_json没必要传了,只需要user_inf_json就够了,这里只是为了演示!







def user_info(request):

    global RADOM_STATE

    REDIRECT_URI = "http://www.xxxxxxxxx.cn/weixin/oauth/"         #跳转的地址
    SCOPE = "snsapi_userinfo"
    STATE = str(random.randint(1000,9999))        #生成随机数
    RADOM_STATE = STATE                            #随机数给全局变量
    redirect_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={}&redirect_uri={}&response_type=code&scope={}&state={}&connect_redirect=1#wechat_redirect".format(APP_ID, REDIRECT_URI, SCOPE, STATE)
    # print(redirect_url)
    return HttpResponseRedirect(redirect_url)    #向微信服务器做跳转

user_info.html模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>yoooko.cn</title>
</head>
<body>

    <h1>Hello</h1>
    <h3>oauth_state: </h3><p>{{ oauth_info.oauth_state }}</p>
    <h3>oauth_code: </h3><p>{{ oauth_info.oauth_code }}</p>

    <p><b>{{ user_info }}</b></p>


</body>
</html>
原文地址:https://www.cnblogs.com/skysowe/p/9330423.html