微信端自动授权登陆实现 - 无第三方库版

鉴于美国国防部网站 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,这个强大的通用于所有开放平台的授权机制。

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

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