1. 程式人生 > >微信網頁授權登入demo

微信網頁授權登入demo

 

第一步:使用者同意授權,獲取code

在確保微信公眾賬號擁有授權作用域(scope引數)的許可權的前提下(服務號獲得高階介面後,預設擁有scope引數中的snsapi_base和snsapi_userinfo),引導關注者開啟如下頁面:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“該連結無法訪問”,請檢查引數是否填寫錯誤,是否擁有scope引數對應的授權作用域許可權。

尤其注意:由於授權操作安全等級較高,所以在發起授權請求時,微信會對授權連結做正則強匹配校驗,如果連結的引數順序不對,授權頁面將無法正常訪問

參考連結(請在微信客戶端中開啟此連結體驗):
scope為snsapi_base
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
scope為snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

尤其注意:跳轉回調redirect_uri,應當使用https連結來確保授權code的安全性。

引數說明

引數 是否必須 說明
appid 公眾號的唯一標識
redirect_uri 授權後重定向的回撥連結地址, 請使用 urlEncode 對連結進行處理
response_type 返回型別,請填寫code
scope 應用授權作用域,snsapi_base (不彈出授權頁面,直接跳轉,只能獲取使用者openid),snsapi_userinfo (彈出授權頁面,可通過openid拿到暱稱、性別、所在地。並且, 即使在未關注的情況下,只要使用者授權,也能獲取其資訊 )
state 重定向後會帶上state引數,開發者可以填寫a-zA-Z0-9的引數值,最多128位元組
#wechat_redirect 無論直接開啟還是做頁面302重定向時候,必須帶此引數

使用者同意授權後

如果使用者同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。

code說明 : code作為換取access_token的票據,每次使用者授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期。

錯誤返回碼說明如下:

返回碼 說明
10003 redirect_uri域名與後臺配置不一致
10004 此公眾號被封禁
10005 此公眾號並沒有這些scope的許可權
10006 必須關注此測試號
10009 操作太頻繁了,請稍後重試
10010 scope不能為空
10011 redirect_uri不能為空
10012 appid不能為空
10013 state不能為空
10015 公眾號未授權第三方平臺,請檢查授權狀態
10016 不支援微信開放平臺的Appid,請使用公眾號Appid

 

第二步:通過code換取網頁授權access_token

首先請注意,這裡通過code換取的是一個特殊的網頁授權access_token,與基礎支援中的access_token(該access_token用於呼叫其他介面)不同。公眾號可通過下述介面來獲取網頁授權access_token。如果網頁授權的作用域為snsapi_base,則本步驟中獲取到網頁授權access_token的同時,也獲取到了openid,snsapi_base式的網頁授權流程即到此為止。

尤其注意:由於公眾號的secret和獲取到的access_token安全級別都非常高,必須只儲存在伺服器,不允許傳給客戶端。後續重新整理access_token、通過access_token獲取使用者資訊等步驟,也必須從伺服器發起。

請求方法

獲取code後,請求以下連結獲取access_token:  https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

引數說明

引數 是否必須 說明
appid 公眾號的唯一標識
secret 公眾號的appsecret
code 填寫第一步獲取的code引數
grant_type 填寫為authorization_code

返回說明

正確時返回的JSON資料包如下:

{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }
引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
expires_in access_token介面呼叫憑證超時時間,單位(秒)
refresh_token 使用者重新整理access_token
openid 使用者唯一標識,請注意,在未關注公眾號時,使用者訪問公眾號的網頁,也會產生一個使用者和公眾號唯一的OpenID
scope 使用者授權的作用域,使用逗號(,)分隔

錯誤時微信會返回JSON資料包如下(示例為Code無效錯誤):

{"errcode":40029,"errmsg":"invalid code"}

 

第三步:重新整理access_token(如果需要)

由於access_token擁有較短的有效期,當access_token超時後,可以使用refresh_token進行重新整理,refresh_token有效期為30天,當refresh_token失效之後,需要使用者重新授權。

請求方法

獲取第二步的refresh_token後,請求以下連結獲取access_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
引數 是否必須 說明
appid 公眾號的唯一標識
grant_type 填寫為refresh_token
refresh_token 填寫通過access_token獲取到的refresh_token引數

返回說明

正確時返回的JSON資料包如下:

{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }
引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
expires_in access_token介面呼叫憑證超時時間,單位(秒)
refresh_token 使用者重新整理access_token
openid 使用者唯一標識
scope 使用者授權的作用域,使用逗號(,)分隔

錯誤時微信會返回JSON資料包如下(示例為code無效錯誤):

{"errcode":40029,"errmsg":"invalid code"}

 

第四步:拉取使用者資訊(需scope為 snsapi_userinfo)

如果網頁授權作用域為snsapi_userinfo,則此時開發者可以通過access_token和openid拉取使用者資訊了。

請求方法

http:GET(請使用https協議) https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

引數說明

引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
openid 使用者的唯一標識
lang 返回國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語

返回說明

正確時返回的JSON資料包如下:

{    "openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl":    "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
引數 描述
openid 使用者的唯一標識
nickname 使用者暱稱
sex 使用者的性別,值為1時是男性,值為2時是女性,值為0時是未知
province 使用者個人資料填寫的省份
city 普通使用者個人資料填寫的城市
country 國家,如中國為CN
headimgurl 使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),使用者沒有頭像時該項為空。若使用者更換頭像,原有頭像URL將失效。
privilege 使用者特權資訊,json 陣列,如微信沃卡使用者為(chinaunicom)
unionid 只有在使用者將公眾號繫結到微信開放平臺帳號後,才會出現該欄位。

錯誤時微信會返回JSON資料包如下(示例為openid無效):

{"errcode":40003,"errmsg":" invalid openid "}

 

附:檢驗授權憑證(access_token)是否有效

請求方法

http:GET(請使用https協議) https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID

引數說明

引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
openid 使用者的唯一標識

返回說明
正確的JSON返回結果:

{ "errcode":0,"errmsg":"ok"}

錯誤時的JSON返回示例:

{ "errcode":40003,"errmsg":"invalid openid"}

 

class weixin {

    private $token = '';
    private $appid = '';
    private $appkey = '';
    private $weObj = '';

    /**
     * 建構函式
     *
     * @param unknown $app            
     * @param string $access_token            
     */
    public function __construct($conf) {
        $this->token = $conf['token'];
        $this->appid = $conf['app_id'];
        $this->appsecret = $conf['app_secret'];

        $config['token'] = $this->token;
        $config['appid'] = $this->appid;
        $config['appsecret'] = $this->appsecret;

        $this->weObj = new Wechat($config);
    }

    /**
     * 授權登入地址
     */
    public function act_login($info, $url){
        // 微信瀏覽器瀏覽
        if (is_wechat_browser() && ($_SESSION['user_id'] === 0 || empty($_SESSION['openid']))) {
            return $this->weObj->getOauthRedirect($url, 1);
        }
        else{
            show_message("請在微信內訪問或者已經登入。", L('relogin_lnk'), url('login', array(
                'referer' => urlencode($this->back_act)
                    )), 'error');
        }
    }

    /**
     * 登入處理
     */
    public function call_back($info, $url, $code, $type){
        if (!empty($code)) {
            $token = $this->weObj->getOauthAccessToken();
            $userinfo = $this->weObj->getOauthUserinfo($token['access_token'], $token['openid']);
            $_SESSION['wechat_user'] = empty($userinfo) ? array() : $userinfo;
			if(!empty($userinfo)){
				//公眾號資訊
				$wechat = model('Base')->model->table('wechat')->field('id, oauth_status')->where(array('type'=>2, 'status'=>1, 'default_wx'=>1))->find();
				$this->update_weixin_user($userinfo, $wechat['id'], $this->weObj);
			}else{
				return false;
			}
            if(!empty($_SESSION['redirect_url'])){
                return array('url'=>$_SESSION['redirect_url']);
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * 更新微信使用者資訊
     *
     * @param unknown $userinfo          
     * @param unknown $weObj            
     */
    public function update_weixin_user($userinfo, $wechat_id = 0, $weObj)
    {
        $time = time();
        $ret = model('Base')->model->table('wechat_user')->field('openid, ect_uid')->where('openid = "' . $userinfo['openid'] . '"')->find();
        if (empty($ret)) {
            //微信使用者繫結會員id
            $ect_uid = 0;
            //檢視公眾號是否繫結
            if($userinfo['unionid']){
                $ect_uid = model('Base')->model->table('wechat_user')->field('ect_uid')->where(array('unionid'=>$userinfo['unionid']))->getOne();
            }

            //未繫結
            if(empty($ect_uid)){
                // 設定的使用者註冊資訊
                $register = model('Base')->model->table('wechat_extend')
                    ->field('config')
                    ->where('enable = 1 and command = "register_remind" and wechat_id = '.$wechat_id)
                    ->find();
                if (! empty($register)) {
                    $reg_config = unserialize($register['config']);
                    $username = msubstr($reg_config['user_pre'], 3, 0, 'utf-8', false) . time().mt_rand(1, 99);
                    // 密碼隨機數
                    $rs = array();
                    $arr = range(0, 9);
                    $reg_config['pwd_rand'] = $reg_config['pwd_rand'] ? $reg_config['pwd_rand'] : 3;
                    for ($i = 0; $i < $reg_config['pwd_rand']; $i ++) {
                        $rs[] = array_rand($arr);
                    }
                    $pwd_rand = implode('', $rs);
                    // 密碼
                    $password = $reg_config['pwd_pre'] . $pwd_rand;
                    // 通知模版
                    $template = str_replace(array(
                        '[$username]',
                        '[$password]'
                    ), array(
                        $username,
                        $password
                    ), $reg_config['template']);
                } else {
                    $username = 'wx_' . time().mt_rand(1, 99);
                    $password = 'ecmoban';
                    // 通知模版
                    $template = '預設使用者名稱:' . $username . "\r\n" . '預設密碼:' . $password;
                }
                // 會員註冊
                $domain = get_top_domain();
                if (model('Users')->register($username, $password, $username . '@' . $domain, array('parent_id'=>intval($_GET['u']))) !== false) {
                    model('Users')->update_user_info();
                } else {
                    die('授權失敗,如重試一次還未解決問題請聯絡管理員');
                }
                $data1['ect_uid'] = $_SESSION['user_id'];
            }
            else{
                //已繫結
                $username = model('Base')->model->table('users')->field('user_name')->where(array('user_id'=>$ect_uid))->getOne();
                $template = '您已擁有帳號,使用者名稱為'.$username;
                $data1['ect_uid'] = $ect_uid;
            }
            
            // 獲取使用者所在分組ID
            $group_id = $weObj->getUserGroup($userinfo['openid']);
            $group_id = $group_id ? $group_id : 0;

            $data1['wechat_id'] = $wechat_id;
            $data1['subscribe'] = 0;
            $data1['openid'] = $userinfo['openid'];
            $data1['nickname'] = $userinfo['nickname'];
            $data1['sex'] = $userinfo['sex'];
            $data1['city'] = $userinfo['city'];
            $data1['country'] = $userinfo['country'];
            $data1['province'] = $userinfo['province'];
            $data1['language'] = $userinfo['country'];
            $data1['headimgurl'] = $userinfo['headimgurl'];
            $data1['subscribe_time'] = $time;
            $data1['group_id'] = $group_id;
            $data1['unionid'] = $userinfo['unionid'];
            
            model('Base')->model->table('wechat_user')->data($data1)->insert();
        } else {
            //開放平臺有privilege欄位,公眾平臺沒有
            unset($userinfo['privilege']);
            model('Base')->model->table('wechat_user')->data($userinfo)->where(array('openid'=> $userinfo['openid']))->update();
            $new_user_name = model('Base')->model->table('users')->field('user_name')->where(array('user_id'=>$ret['ect_uid']))->getOne();
            ECTouch::user()->set_session($new_user_name);
            ECTouch::user()->set_cookie($new_user_name);
            model('Users')->update_user_info();
        }
        $_SESSION['openid'] = $userinfo['openid'];
        setcookie('openid', $userinfo['openid'], gmtime() + 86400 * 7);
    }

}