微信端自動授權登陸實現 - 無第三方庫版

鑑於美國國防部網站 http://sxsxssx.com 是用yii開發的,自然的我新建了一個wechat模塊,域名 http://sxsxssx.com/wechat 是微信端部分。

對於在微信端登陸的每個會員,都自動生成一個新會員,下次登陸直接使用。

開始改造

鑑於這樣一個小站,我決定直接改造他的user表。老的是

大家知道微信有個 open_id 代表每個公衆號的會員,每個人針對於每個公衆號都是不一樣的,於是我爲user表增加了一個 open_id 字段,用來標記會員。對於 open_id 爲 NULL 時,代表不來自於微信端。

關於 open_id 長度問題,迄今爲止微信也沒有給出此字段應該多長,經多方查證和統計,一般在28、29、30之間徘徊,所以針對此字段,設置爲32位的 varchar 應該是沒啥問題的,事實上北哥也一直這樣乾的。

@@nai8@@

微信網頁授權

接下來我們要研究當一個人通過微信瀏覽器打開客戶網站的時候,如何識別身份那?看微信網頁授權文檔 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141217&token=&lang=zh_CN

先讀一遍文檔再往下看。

**簡單的說:**如果用戶在微信瀏覽器中訪問第三方網頁,可以通過微信網頁授權機制,來獲取用戶基本信息,進而實現業務邏輯。

換句話說,就是我們有機會獲得當前正在瀏覽網頁的會員基本信息,那麼思路就來了,草草的畫個流程圖。

大體內容是這樣的,我將微信授權後的open_id存到session中,下次訪問頁面時,如果session中有open_id則根據它從數據庫中去對應會員信息然後繼續訪問頁面,如果session中open_id不存在,則進入微信授權獲得open_id,然後看看數據庫中是否有對應會員,如果有則拿出來然後將open_id再存到session,如果沒有則新建一個含open_id的會員,然後存session。

所以其實重點就落在了獲取open_id上,通過微信的web授權文檔,我知道它是一定拿的到的,我先把上面流程圖的代碼實現下。

考慮到對於wechat模塊,所有頁面我都需要在web授權下去訪問,因此對於wechat/controllers 我抽象出一個父類,父類完成微信的授權和獲取當前訪問的微信會員信息等職責。

構造WxBase

我決定爲WxBase增加一個叫做$wxLogin的變量,存放當前獲取的微信會員。代碼如下

namespace app\modules\wechat\controllers;

use Yii;
use yii\web\Controller;
class WxBase extends Controller {

    public $wxLogin = null;

    public function init(){
        parent::init();

        $this->initWxAuth(); // 獲取登陸會員信息
    }

    protected function initWxAuth(){
        $session = Yii::$app->session;
        $wxOpenId = $session["wx_open_id"];
        if(empty($wxOpenId)){
            // todo 去做授權機制
            die("我要去授權,別攔我。");
        }

        $user = User::find()->where(['open_id'=>$wxOpenId])->one();
        $this->wxLogin = $user;
    }
}

上面的邏輯其實是模擬了用戶 登陸 / 註冊 的思路,只不過這種 登陸 / 註冊 是微信web授權授權而已。

好,接下來我們主攻如何通過微信web授權完成 open_id 的獲取以及會員初始化的問題。

真正核心的東西來了

通過對文檔的閱讀,我明白需要做幾項配置。

  • 先到公衆平臺官網修改授權回調域名。
  • 我們需要知道公衆號的 AppID 和 AppSecret

好在我們的上一篇讓大家準備了 微信公衆平臺接口測試帳號 ,足以滿足。

存放 AppID 和 AppSecret

考慮到 AppID 和 AppSecret 屬於配置項,且將來可能具有更多的配置,因此我在程序的 config/params.php 中 新建了一個數組key用於存放所有的微信相關參數。

return [
    'adminEmail' => '[email protected]',

    'wechat'=>[
        'appId'=>'appId-Key',
        'appSecret'=>'appSecret-Key',
    ]
];

配置回調域名

對於測試號 我們可以通過 體驗接口權限表 的 網頁授權獲取用戶基本信息 找到它

對於正式環境 需要在“開發 -接口權限 - 網頁服務 - 網頁帳號 - 網頁授權獲取用戶基本信息”的配置選項中。

正式服務器需要驗證服務器可靠性,下載一個txt文件到你服務器根目錄然後檢查通過才能提交。有時候微信服務器可能抽風,即使你按步驟做了,通過瀏覽器也能訪問上傳的txt文件內容,但是仍然不通過,一般過會再試就沒事了。

natapp映射本地

然後我通過natapp映射遠程域名到我本地目錄,一切就緒了。

我映射的域名爲 http://abei.natapp2.cc 到 本地。

好,現在我用微信開發者工具 訪問 http://abei.natapp2.cc/index.php?r=wechat

我要去授權,別攔我。,哈哈,要的就是它。好,我開始寫授權。

先看下微信給我們的步驟

  • 第一步:用戶同意授權,獲取code
  • 第二步:通過code換取網頁授權access_token
  • 第三步:刷新access_token(如果需要)
  • 第四步:拉取用戶信息(需scope爲 snsapi_userinfo)
  • 附:檢驗授權憑證(access_token)是否有效

用戶同意授權,獲取code

namespace app\modules\wechat\controllers;

use Yii;
use yii\web\Controller;
class WxBase extends Controller {

    public $wxLogin = null;

    ......

    protected function initWxAuth(){
        $session = Yii::$app->session;
        $wxOpenId = $session["wx_open_id"];
        if(empty($wxOpenId)){
            // todo 去做授權機制
            $this->wxRedirectOauth2();
        }

        $user = User::find()->where(['open_id'=>$wxOpenId])->one();
        $this->wxLogin = $user;
    }

    protected function wxRedirectOauth2(){
        $url = Yii::$app->request->getUrl();
        $conf = Yii::$app->params['wechat'];
        $redirect_url = Url::to(['/wechat/default/oauth2','url'=>$url],true);
        $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=".$conf['appId']."&redirect_uri=".urlencode($redirect_url)."&response_type=code&scope=snsapi_userinfo&state=#wechat_redirect";
        header('Location:'.$url);
        exit();
    }   
}

上面的代碼我是想獲得用戶授權,並且得到code。我們使用index.php?r=wechat/default/oauth2 作爲授權後重定向的回調鏈接地址。爲了教學方便,我採用了snsapi_userinfo作用域,它會彈出一個授權對話框。

少言,看效果。

在邏輯上看,用戶點擊確認登陸後,微信會引導訪問一個叫做 http://abei.natapp2.cc/index.php?r=wechat/default/oauth2&url=xxxx 的頁面。

但是這樣存在一個問題,原則上這個地址是用來獲取code的,但是因爲我們做了一個父類WxBase,並且針對每一個 wechat 模塊下的action,我們都做了 initWxAuth 工作,那就會導致當yii訪問 wechat/default/oauth2 時候發現 Session 中不存在 wx_open_id 不存在,然後繼續訪問 我們剛定義的 wxRedirectOauth2函數,形成死循環,頁面無限刷新。

因此我們需要將 wechat/default/oauth2 這個action排除在 initWxAuth 之外,所以我們在授權頁面 確認登陸 前再對代碼進行一點小修改。

namespace app\modules\wechat\controllers;

use Yii;
use yii\web\Controller;
class WxBase extends Controller {

    public $wxLogin = null;

    public $notAuths = [
        'wechat/default/oauth2'
    ];

    public function init(){
        parent::init();

        if(in_array(Yii::$app->requestedRoute,$this->notAuths) == false){
            $this->initWxAuth();
        }
    }

    ......
}

考慮程序擴展性,我將不需要授權的路由放到了一個數組中(你甚至可將其作爲參數配置,那樣更好。)

順便我們寫一下 wechat/default/oauth2

namespace app\modules\wechat\controllers;

class DefaultController extends WxBase {
    /**
     * Renders the index view for the module
     * @return string
     */
    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionOauth2(){
        echo "我是oauth2";
    }
}

好,再一次看效果。

很好,一起都在控制中,的確微信引導我們來到了 wechat/default/oauth2 ,並且我們來看看 這個url

http://abei.natapp2.cc/index.php?r=wechat%2Fdefault%2Foauth2&url=%2Findex.php%3Fr%3Dwechat&code=051af2HX0141A02cTLJX02hJGX0af2Hw&state=

你是否發現,多了url、code、state,那麼他們是什麼那?

  • url 是我們的 oauth2 處理完邏輯後進入的頁面,也就是我本次要訪問的目標頁面。
  • code 這個好,就是我要的,通過它我能得到access token,進而獲取open_id等。
  • state 一個可以傳遞信息的字段,這裏沒有使用。

通過code換取網頁授權access_token

接下來的好戲都有oauth2 這個action來主導,我們先去獲得access_token。

namespace app\modules\wechat\controllers;

class DefaultController extends WxBase {
    ....

    public function actionOauth2(){

        //  得到Get傳遞過來的code 和 url
        $code = Yii::$app->request->get('code');
        $url = Yii::$app->request->get('url');

        $conf = Yii::$app->params['wechat'];
        $accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$conf['appId']}&secret={$conf['appSecret']}&code=".$code."&grant_type=authorization_code";
        $result = $this->HttpRequest($accessTokenUrl);
        echo $result;
    }

    /**
     * 使用curl獲取url返回結果。
     * @param $url
     * @return mixed
     */
    public function HttpRequest($url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
        curl_setopt($ch, CURLOPT_TIMEOUT, 2);
        $result = curl_exec($ch);
        return $result;
    }
}

根據文檔我將appId、appSecret和這次的code傳遞給微信服務器以請求授權access_token,爲了看的方便,我將微信返回的結果直接打印下來。

這裏的 HttpRequest 是通過curl在php發起的GET 請求,它當前可以使用,但是如果用於生產環境,請再優化。

看下微信返回的。

結果讓人欣慰,微信給我返回了 access_token、openid、expires_in 等等。參數的解釋如下圖

對於unionid,必須將公衆號綁定到開放平臺纔會有,具體作用以後會有說,這裏先過。

看看我們現在

我們已經得到了 openid 不是麼?馬上去完善代碼。

namespace app\modules\wechat\controllers;

class DefaultController extends WxBase {
    ....

    public function actionOauth2(){

        //  得到Get傳遞過來的code 和 url
        $code = Yii::$app->request->get('code');
        $url = Yii::$app->request->get('url');

        $conf = Yii::$app->params['wechat'];
        $accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$conf['appId']}&secret={$conf['appSecret']}&code=".$code."&grant_type=authorization_code";
        $result = $this->HttpRequest($accessTokenUrl);
        $data = Json::decode($result);
        if($data && isset($data['errcode']) === false){
            $model = User::find()->where(['open_id' => $data['openid']])->one();
            if($model === false){
                $model = new User();
                $model->username = "wx-".rand(100000,999999);
                $model->password = md5('123456');
                $model->create_time = time();
                $model->open_id = $data['openid'];
                $model->save();
            }

            $session = \Yii::$app->session;
            $session['wx_open_id'] = $data['openid'];
            return $this->redirect($url);
        }
    }

    ...
}

看下效果吧。

頁面經過授權後進入了我們訪問的目標頁面,再看看數據庫。

成功了~~~~

完事了麼?

從代碼上說,我們已經完成了上面流程圖的內容,登陸頁面可以對新會員進行初始化,並且可以在每個頁面獲得當前登陸的會員信息($this->wxLogin中),但是你也注意到了,在會員初始化的時候,username等等我們是用了隨機值,而微信在授權的最後一步是允許我們通過access_token拉取用戶信息的,我們實現一下。

拉取用戶信息(需scope爲 snsapi_userinfo)

在我的數據庫設計中,username是登陸用戶名,必須唯一,因此放微信信息的暱稱是不合理的,下面代碼只將解信息拉取過程,具體你要講拉取的信息放到何處具體程序具體看要。

namespace app\modules\wechat\controllers;

class DefaultController extends WxBase {
    ....

    public function actionOauth2(){

        //  得到Get傳遞過來的code 和 url
        $code = Yii::$app->request->get('code');
        $url = Yii::$app->request->get('url');

        $conf = Yii::$app->params['wechat'];
        $accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$conf['appId']}&secret={$conf['appSecret']}&code=".$code."&grant_type=authorization_code";
        $result = $this->HttpRequest($accessTokenUrl);
        $data = Json::decode($result);
        if($data && isset($data['errcode']) === false){
            $model = User::find()->where(['open_id' => $data['openid']])->one();
            if($model === false){

                $model = new User();
                $model->username = "wx-".rand(100000,999999);
                $model->password = md5('123456');
                $model->create_time = time();
                $model->open_id = $data['openid'];
                $model->save();

                // 拉取用戶信息
                $userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token={$data['access_token']}&openid={$data['openid']}&lang=zh_CN";
                $info = $this->HttpRequest($userInfoUrl);
                $userInfo = Json::decode($info);
                // todo $userInfo 是一個數組,具體放哪具體決定,可以一個表單獨存放會員微信信息,也可以對user表進行擴展。
            }

            $session = \Yii::$app->session;
            $session['wx_open_id'] = $data['openid'];
            return $this->redirect($url);
        }
    }

    ...
}

如上代碼,你可以輕鬆獲得用戶信息。

最後要說的

上面的代碼經阿北測試可以正常運行,但是如果生產環境還請對每個部分做下異常處理,在平時開發中,這種方式並不是我們推薦的,用一個開源庫也許是我們最好的選擇,本專題也會講到用開源庫easywechat實現微信端授權登陸的實現。

如果你是兄弟連成員,務必關注“開發知乎”這套視頻課程,這是一個用easywechat完整且徹底實現微信接口開發的好案例。

另外你可能對本文的code、access_token等比較迷糊還是,無需憂慮,這些都是oauth2授權機制的一部分,我們會單獨去講解 oauth2,這個強大的通用於所有開放平臺的授權機制。

而本篇,你能做出微信端授權就已成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章