ドキュメント:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319
WeChatの認証ログインは、QQやSinaなどのプラットフォームの認証ログインとほぼ同じで、いずれもOAuth2.0認証方式を採用しています。
WeChatの認証は2種類に分かれています:
サイレント認証 ポップアップ認証、ユーザーの手動同意が必要 2種類のscopeの違いについて
snsapi_baseをscopeとして開始されるウェブ認証は、ページにアクセスしたユーザーのopenidを取得するためのもので、サイレント認証であり、自動的にコールバックページにリダイレクトされます。ユーザーは直接コールバックページ(通常は業務ページ)にアクセスしたように感じます。 snsapi_userinfoをscopeとして開始されるウェブ認証は、ユーザーの基本情報を取得するためのものです。この認証はユーザーの手動同意が必要ですが、一度同意すれば、公式アカウントをフォローしていなくても、認証後にそのユーザーの基本情報を取得できます。 ユーザー管理関連のAPIにおける「ユーザー基本情報取得API」は、ユーザーが公式アカウントとメッセージのやり取りをするか、フォローした後のイベントプッシュがあって初めて、ユーザーのOpenIDに基づいて基本情報を取得できます。このAPIを含む他のWeChat APIは、すべて対象ユーザー(openid)が公式アカウントをフォローしている場合にのみ、正常に呼び出すことができます。
具体的には、ウェブ認証のフローは4つのステップに分かれています:
ユーザーを認証ページに誘導し、同意を得てcodeを取得する codeを使用してウェブ認証用のaccess_tokenを取得する(基本サポートのaccess_tokenとは異なる) 必要に応じて、開発者はウェブ認証用のaccess_tokenを更新し、期限切れを回避する ウェブ認証用のaccess_tokenとopenidを使用してユーザーの基本情報を取得する(UnionIDメカニズムをサポート) 以下はカプセル化されたWeChat操作クラスです。access_tokenとticketを保存するために2つのデータテーブルを使用します。これらには有効期限があり、1日あたりのリクエスト数に上限があるため、開発者は自身で保存する必要があります。
<?php /** * WeChat操作テーブル * wxtoken テーブル構造 * id * access_token * addtime * wxticket テーブル構造 * id * ticket * addtime */ class WX { private $appid; private $appserect; private $curl; private $msg; protected $errs = array( '-1' => 'システムがビジーです。開発者はしばらくしてから再試行してください', '0' => 'リクエスト成功', '40001' => 'AppSecretが間違っているか、この公式アカウントのものではありません。開発者はAppSecretが正しいことを確認してください', '40002' => 'grant_typeフィールドの値がclient_credentialであることを確認してください', '40164' => 'APIを呼び出したIPアドレスがホワイトリストにありません。APIのIPホワイトリストで設定してください。', ); function __construct($appid, $appserect) { $this->appid = $appid; $this->appserect = $appserect; $this->curl = new Curl(); } /* WeChatウェブ認証ログイン 公式アカウント設定 - 機能設定 - ウェブ認証ドメインでの設定が必要 第一歩:ユーザーが認証に同意し、codeを取得 scope : snsapi_base openidのみ取得可能、直接リダイレクト snsapi_userinfo */ public function getCode($redirect_uri, $scope = 'snsapi_userinfo',$state = '1') { $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->appid}&redirect_uri={$redirect_uri}&response_type=code&scope={$scope}&state={$state}#wechat_redirect"; header("Location:{$url}"); exit; } /* 第二歩:codeを使用してウェブ認証用のaccess_tokenを取得 */ public function getAccessTokenByCode($code) { $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->appserect}&code={$code}&grant_type=authorization_code"; // exit($url); // $curl = new Curl(); $result = $this->curl->doGet($url); if (!$result) { // $this->curl->getError() $this->msg = "トークンの取得に失敗しました"; return false; } $result = json_decode($result, true); if ($result['errcode']) { $this->msg = $result['errmsg']; return false; } return $result; } // 第三歩:access_tokenを更新(必要な場合) code経由でopenidを取得 $type 0サイレント認証 1ポップアップ認証 public function getUserInfo($code, $type = 0, $lang = 'zh_CN ') { $result = $this->getAccessTokenByCode($code); if (!$result) { return false; } $member = C::t(PT_USER)->getByOpenid($result['openid']); if ($member) { return $member; } else { if ($type) { $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$result['access_token']}&openid={$result['openid']}&lang={$lang}"; // $return = $this->curl->doGet($url); // このAPIは挙動がおかしく、強制的にファイルヘッダーを表示します $return = file_get_contents($url); if (!$return) { $this->msg = 'ユーザー情報の取得に失敗しました'; return false; } $return = json_decode($return, true); if (!$return) { $this->msg = 'ユーザー情報の取得リターンに失敗しました'; return false; } // file_put_contents('ccc.txt',print_r($return,true),FILE_APPEND); $data = array( 'openid' => $return['openid'], 'name' => $return['nickname'], 'sex' => $return['sex'], 'province' => $return['province'], 'city' => $return['city'], 'country' => $return['country'], 'img' => $return['headimgurl'], 'bindtel' => 0, ); } else { $data = array( 'openid' => $result['openid'], 'username' => "WeChatユーザー_" . random(6,1) ); } $name = rand(100000, 1000000000); $e = $name . "@qq.com"; $password = $e; $id = UserAddEdit(0, $data['username'], $password, $e,10,0,"", $msg); if ($id <= 0) { $this->msg = $msg; return false; } C::t(PT_USER)->update($data, $id); $member = C::t(PT_USER)->get($id); return $member; } } /* 公式アカウント セキュリティセンターでIPホワイトリストを設定 公式アカウントのグローバルでユニークなAPI呼び出し資格情報。公式アカウントが各APIを呼び出す際にはaccess_tokenを使用する必要があります。開発者は適切に保存する必要があります。access_tokenの保存には少なくとも512文字のスペースを確保してください。access_tokenの有効期間は現在2時間で、定期的に更新する必要があります。重複して取得すると、前回取得したaccess_tokenが無効になります。 */ public function getAccessToken($type) { $addtime = TIMESTAMP - 7200; $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appid}&secret={$this->appserect}"; $row = C::t(PT_WXTOKEN)->getNew($addtime, $type); if ($row) { return $row['access_token']; } else { $result = $this->curl->doGet($url); if (!$result) { $this->msg = "トークンコンテンツを取得できません"; return false; } $result = json_decode($result, true); if (!$result) { $this->msg = "トークンコンテンツの解析に失敗しました"; return false; } if ($result['access_token']) { C::t(PT_WXTOKEN)->addToken($result['access_token'], $type); return $result['access_token']; } else { $this->msg = "トークンの取得に失敗しました"; return false; } } } // JSチケットを取得 公式アカウント設定 - 機能設定 - JSインターフェースセキュアドメインでの設定が必要 public function getJsTicket() { $addtime = TIMESTAMP - 7200; $row = C::t(PT_WXTICKET)->getNew($addtime); if ($row) { return $row['ticket']; } else { $token = $this->getAccessToken(); if (!$token) { return false; } $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$token}&type=jsapi"; $result = $this->curl->doGet($url); if (!$result) { $this->msg = "JSチケットを取得できません"; return false; } $result = json_decode($result, true); if (!$result) { $this->msg = "JSチケットコンテンツの解析に失敗しました"; return false; } if ($result['ticket']) { C::t(PT_WXTICKET)->addTicket($result['ticket']); return $result['ticket']; } else { $this->msg = "JSチケットの取得に失敗しました"; return false; } } } // js sdk チケット署名 現在のウェブページのURL、#およびそれ以降の部分は含まない public function jsSign($data) { // 1.署名対象のすべてのパラメータをフィールド名のASCIIコード順(辞書順)でソートする ksort($data); // 2.URLキー・バリューペアの形式(つまりkey1=value1&key2=value2…)で文字列string1に連結する。元の値を使用し、URLエンコードは行わない $string1 = $this->ToUrlParams($data); // echo "string1:{$string1}<br/>"; // 3.string1をsha1で暗号化する $sign = sha1($string1); // echo "signature:{$sign}<br/>"; return $sign; } // メッセージ内容を取得 public function getMsg() { return $this->msg; } /** * パラメータをURLパラメータ形式にフォーマットする */ public function ToUrlParams($data) { $buff = ""; foreach ($data as $k => $v) { if ($k != "sign" && $v != "" && !is_array($v)) { $buff .= $k . "=" . $v . "&"; } } $buff = trim($buff, "&"); return $buff; } } ?> // WeChatログイン function wxlogin() { global $_G,$identifier,$config,$wx; if (!$_G['uid']) { if ($_GET['state']) { //コールバック $member = $wx->getUserInfo($_GET['code']); if (!$member) { exit($wx->getMsg()); } if (!function_exists("setloginstatus")) { include_once libfile('function/member'); } // ログイン状態を設定$wx setloginstatus($member, 2592000); checkfollowfeed(); $_G['uid'] = $member['uid']; $_G['member'] = $member; } else { //認証をリクエスト パラメータをエンコード $redirect = urlencode(getProtocol() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); $wx->getCode($redirect, 'snsapi_base'); } } } function getProtocol() { return is_HTTPS() ? 'https://' : 'http://'; } function is_HTTPS() { if ($_SERVER['HTTPS'] === 1 || $_SERVER['HTTPS'] === 'on' || $_SERVER['SERVER_PORT'] == 443) { return true; } return false; }