解決微信公眾號OAuth出現40029(invalid code,不合法的oauth_code)的錯誤

SZW發表於2016-09-15

關於OAuth

官方教程:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842&token=&lang=zh_CN

原理及基本開發思路:http://www.cnblogs.com/szw/p/3764275.html

現象

在使用公眾號的OAuth過程中,我們有時會碰到40029(invalid code,不合法的oauth_code)的錯誤。

原因

其實通過官方提供的API獲取的CODE通常是不會有問題的,不可用是因為這個CODE被悄悄地用掉了。

通過微信Web開發工具跟蹤可以看到,微信發起了2次“相同”的請求,第一次請求被其終止掉了(也就是我們主動發起的這一次):

兩次OAuth請求

這兩次請求的Url還是有差別的,第一次我們通過“GetAuthorizeUrl”介面獲取到的url如下:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxxx&redirect_uri=xxx&response_type=code&scope=snsapi_userinfo&state=MyState

這次請求會被微信伺服器中斷,然後由微信再自動發起一次:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxxx&redirect_uri=xxx&response_type=code&scope=snsapi_userinfo&state=MyState&uin=MTMyMjE0NDU%3D&key=63e987ba88ddfb44972f00256f262220434daa5ef8d27994eeb1cf525b6ddf2c5fc2aeb69d08087f5c139292417a774e&pass_ticket=SISrTjV8ln27168AZM/sFXkc2yp5i2lm+rwItExPL+PxZBu2/GXq1MbUp6BvmnWAB0KtpV9nypybsh41CV2SQA=

對比可以發現第二次請求多了兩個引數:

&uin=MTMyMjE0NDU%3D&key=63e987ba88ddfb44972f00256f262220434daa5ef8d27994eeb1cf525b6ddf2c5fc2aeb69d08087f5c139292417a774e
&pass_ticket=SISrTjV8ln27168AZM/sFXkc2yp5i2lm+rwItExPL+PxZBu2/GXq1MbUp6BvmnWAB0KtpV9nypybsh41CV2SQA=

這兩個引數應該也是出於的安全的需要,但是這麼一來,給開發者的伺服器就會帶來困擾:
第一次請求雖然微信伺服器終止了,但是開發者伺服器還在執行,多數情況下已經使用了redirect_uri,並把傳遞過來的code使用掉了(code是一次性的),
當第二次請求進來的時候,我們用相同的code自然就失效了。

解決方案一(推薦★☆☆☆☆)

從圖中可以看到,其實兩次請求的發起者是不一樣的,可以從這個角度入手,鑑別正確的請求。

當然這個方法有一定的風險:兩次請求發生的時間間隔非常小(上圖為19毫秒),仍然需要處理非同步的問題。

解決方案二(推薦★★☆☆☆)

這也是網傳的一個方案:在正常獲取了微信官方的url後面,加上&connect_redirect=1這個引數,微信就不會發起第二次,
但是本人測試沒有成功,然收到了兩次。

解決方案三(推薦★★★★☆)

既然第二次請求的引數和第一次不一樣,就可以從uin和pass_ticket兩個引數進行判斷,只接受有這兩個引數的請求。

這種做法的缺點是這個請求引數並沒有體現在官方文件中,或許會悄悄地進行變化,所以需要時刻關注其有效性。

此方案作為一個條件加入到其他方案中還是不錯的。

解決方案四(推薦★★★★★)

利用同步鎖,判斷code的使用情況,這是最粗獷也是最徹底的方法,以 C# 使用 Senparc.Weixin SDK 為例,直接上程式碼:

定義靜態變數:

    static Dictionary<string, OAuthAccessTokenResult> OAuthCodeCollection = new Dictionary<string, OAuthAccessTokenResult>();
    static object OAuthCodeCollectionLock = new object();
        

回撥方法內:

    string openId;
    OAuthAccessTokenResult result = null;

    try
    {
        //通過,用code換取access_token

        var isSecondRequest = false;
        lock (OAuthCodeCollectionLock)
        {
            isSecondRequest = OAuthCodeCollection.ContainsKey(code);
        }

        if (!isSecondRequest)
        {
            //第一次請求
            LogUtility.Weixin.DebugFormat("第一次微信OAuth到達,code:{0}", code);
            lock (OAuthCodeCollectionLock)
            {
                OAuthCodeCollection[code] = null;
            }
        }
        else
        {
            //第二次請求
            LogUtility.Weixin.DebugFormat("第二次微信OAuth到達,code:{0}", code);

            lock (OAuthCodeCollectionLock)
            {
                result = OAuthCodeCollection[code];
            }
        }

        try
        {
            try
            {
                result = result ?? OAuthApi.GetAccessToken(SiteConfig.YourAppId, SiteConfig.YourAppSecret, code);
            }
            catch (Exception ex)
            {
                return Content("OAuth AccessToken錯誤:" + ex.Message);
            }

            if (result != null)
            {
                lock (OAuthCodeCollectionLock)
                {
                    OAuthCodeCollection[code] = result;
                }
            }
        }
        catch (ErrorJsonResultException ex)
        {
            if (ex.JsonResult.errcode == ReturnCode.不合法的oauth_code)
            {
                //code已經被使用過
                lock (OAuthCodeCollectionLock)
                {
                    result = OAuthCodeCollection[code];
                }
            }
        }

        openId = result != null ? result.openid : null;
    }
    catch (Exception ex)
    {
        return Content("授權過程發生錯誤:" + ex.Message);
    }

    //使用result繼續操作

說明:
1、上述靜態Dicitonary的儲存方式適用於單臺伺服器,如果是分散式的系統,這裡的Dictionary請使用公共快取(如Redis),並使用分佈鎖,否則如果兩次請求命中了兩臺不同的伺服器仍然會失效。
2、請注意做好快取清理工作

解決方案總結

以上解決方案沒有絕對的好壞之分,要看具體的環境,因為都不會涉及到影響效率和安全性的問題,可以視情況組合使用。推薦指數更多傾向於通用性。

參考資料

微信開發資源:https://github.com/JeffreySu/WeixinResource/blob/master/%E9%82%A3%E4%BA%9B%E5%B9%B4%E6%88%91%E4%BB%AC%E8%B8%A9%E8%BF%87%E7%9A%84%E5%9D%91/20160913-OAuth%E5%87%BA%E7%8E%B040029(invalid%20code)%E9%94%99%E8%AF%AF.md

微信網頁授權:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842&token=&lang=zh_CN

Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十二):OAuth2.0說明:http://www.cnblogs.com/szw/p/3764275.html

相關文章