微信網頁授權登入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);
}
}