官方教程:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842&token=&lang=zh_CNhtml
原理及基本開發思路:http://www.cnblogs.com/szw/p/3764275.htmlgit
在使用公衆號的OAuth過程當中,咱們有時會碰到40029(invalid code,不合法的oauth_code)的錯誤。github
其實經過官方提供的API獲取的CODE一般是不會有問題的,不可用是由於這個CODE被悄悄地用掉了。api
經過微信Web開發工具跟蹤能夠看到,微信發起了2次「相同」的請求,第一次請求被其終止掉了(也就是咱們主動發起的這一次):緩存
這兩次請求的Url仍是有差異的,第一次咱們經過「GetAuthorizeUrl」接口獲取到的url以下:安全
此次請求會被微信服務器中斷,而後由微信再自動發起一次:微信
對比能夠發現第二次請求多了兩個參數:app
&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繼續操做
說明:
一、上述靜態Dicitonary的儲存方式適用於單臺服務器,若是是分佈式的系統,這裏的Dictionary請使用公共緩存(如Redis),並使用分佈鎖,不然若是兩次請求命中了兩臺不一樣的服務器仍然會失效。
二、請注意作好緩存清理工做
以上解決方案沒有絕對的好壞之分,要看具體的環境,由於都不會涉及到影響效率和安全性的問題,能夠視狀況組合使用。推薦指數更多傾向於通用性。
微信網頁受權: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