From 0ce3dd54cdbd0ec0ec6082b2b9ec3cc2850be4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E4=B8=80=E5=B3=B0?= <1feng.0595@gmail.com> Date: Mon, 6 Nov 2017 00:08:35 +0800 Subject: [PATCH] WechatAuth --- app/Common/WechatAuth.php | 101 ++++++++++++------ app/Http/Controllers/Api/UserController.php | 79 ++++++++++++-- .../Controllers/Weixin/UserController.php | 66 ++++++++---- app/Http/Model/User.php | 8 +- resources/org/wxJsSdk/jssdk.php | 4 +- resources/org/wxJsSdk/wx_sample.php | 89 +++++++++++++++ routes/web.php | 4 +- 7 files changed, 278 insertions(+), 73 deletions(-) create mode 100644 resources/org/wxJsSdk/wx_sample.php diff --git a/app/Common/WechatAuth.php b/app/Common/WechatAuth.php index 53ce6de..bd3d6ec 100644 --- a/app/Common/WechatAuth.php +++ b/app/Common/WechatAuth.php @@ -2,44 +2,77 @@ namespace App\Common; /** - * OAuth2.0微信授权登录实现 - * - * @author FLi - * @文件名:GetWxUserInfo.php - */ + * OAuth2.0微信授权登录实现 + * + */ class WechatAuth { //高级功能->开发者模式->获取 - private $app_id = 'xxx'; - private $app_secret = 'xxxxxxx'; + private $app_id; + private $app_secret; - //$registration_id = getenv('registration_id'); - - public static function send($msg, $param='') + public function __construct($app_id, $app_secret) + { + $this->app_id = $app_id; + $this->app_secret = $app_secret; + } + + /** + * 获取微信授权链接 + * + * @param string $redirect_uri 回调地址,授权后重定向的回调链接地址,请使用urlEncode对链接进行处理 + * @param mixed $state 可以为空,重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 + */ + public function get_authorize_url($redirect_uri = '', $state = '') + { + return "https://open.weixin.qq.com/connect/oauth2/authorize?appid=".$this->app_id."&redirect_uri=".urlencode($redirect_uri)."&response_type=code&scope=snsapi_userinfo&state=".$state."#wechat_redirect"; + } + + /** + * 获取授权token + * + * @param string $code 通过get_authorize_url获取到的code + */ + public function get_access_token($code = '') + { + $token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->app_id}&secret={$this->app_secret}&code={$code}&grant_type=authorization_code"; + $token_data = $this->http($token_url); + + return json_decode($token_data, true); + } + + /** + * 获取授权后的微信用户信息 + * + * @param string $access_token + * @param string $open_id + */ + public function get_user_info($access_token = '', $open_id = '') + { + $info_url = "https://api.weixin.qq.com/sns/userinfo?access_token={$access_token}&openid={$open_id}&lang=zh_CN"; + $info_data = $this->http($info_url); + + return json_decode($info_data, true); + } + + // cURL函数简单封装 + function http($url, $data = null) { - $client = new JPushMsg(self::APP_KEY, self::APP_SECRET, null); - - $push_payload = $client->push(); - $push_payload = $push_payload->setPlatform('all'); - if(isset($param['mobile'])){$push_payload = $push_payload->addAlias(md5($param['mobile']));} - $push_payload = $push_payload->addAllAudience(); - $push_payload = $push_payload->setNotificationAlert($msg); - - try - { - $push_payload->send(); - } - catch (JPushMsg\Exceptions\APIConnectionException $e) - { - Log::info($e); - return false; - } - catch (JPushMsg\Exceptions\APIRequestException $e) - { - Log::info($e); - return false; - } - - return true; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + + if (!empty($data)) + { + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + } + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + $output = curl_exec($curl); + curl_close($curl); + + return $output; } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index bae8163..6157a5d 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -127,12 +127,35 @@ class UserController extends CommonController return ReturnData::create(ReturnData::SUCCESS); } + //登录 + public function wxLogin(Request $request) + { + $data['user_name'] = $request->input('user_name',''); + $data['password'] = $request->input('password',''); + $data['openid'] = $request->input('openid',''); + + if (($data['user_name']=='' && $data['password']=='') || $data['openid']=='') + { + return ReturnData::create(ReturnData::PARAMS_ERROR); + } + + $res = User::wxLogin($data); + + if ($res === false) + { + return ReturnData::create(ReturnData::PARAMS_ERROR,null,'账号或密码错误'); + } + + return ReturnData::create(ReturnData::SUCCESS,$res); + } + //注册 public function wxRegister(Request $request) { $data['mobile'] = $request->input('mobile',''); $data['user_name'] = $request->input('user_name',''); $data['password'] = $request->input('password',''); + $data['parent_id'] = $request->input('parent_id',''); $parent_mobile = $request->input('parent_mobile',''); if (($data['mobile']=='' && $data['user_name']=='') || $data['password']=='') @@ -179,25 +202,59 @@ class UserController extends CommonController return ReturnData::create(ReturnData::SUCCESS,$res); } - //登录 - public function wxLogin(Request $request) - { - $data['user_name'] = $request->input('user_name',''); - $data['password'] = $request->input('password',''); + //微信授权注册 + public function wxOauthRegister(Request $request) + { + $data['openid'] = $data['user_name'] = $request->input('openid',''); + $data['sex'] = $request->input('sex',''); + $data['head_img'] = $request->input('head_img',''); + $data['nickname'] = $request->input('nickname',''); + $data['parent_id'] = $request->input('parent_id',''); + $parent_mobile = $request->input('parent_mobile',''); + $data['mobile'] = $request->input('mobile',''); - if ($data['user_name']=='' || $data['password']=='') + if ($data['openid']=='') { return ReturnData::create(ReturnData::PARAMS_ERROR); } - $res = User::wxLogin($data); + if ($parent_mobile!='') + { + if($user = User::getOneUser(array('mobile'=>$parent_mobile))) + { + $data['parent_id'] = $user->id; + } + else + { + return ReturnData::create(ReturnData::PARAMS_ERROR,null,'推荐人手机号错误'); + } + } - if ($res === false) + if (isset($data['mobile']) && !Helper::isValidMobile($data['mobile'])) { - return ReturnData::create(ReturnData::PARAMS_ERROR,null,'账号或密码错误'); + return ReturnData::create(ReturnData::MOBILE_FORMAT_FAIL); } - - return ReturnData::create(ReturnData::SUCCESS,$res); + + //判断是否已经注册 + if (User::getOneUser(array('mobile'=>$data['mobile']))) + { + return ReturnData::create(ReturnData::MOBILE_EXIST); + } + + if (User::getOneUser(array('openid'=>$data['openid']))) + { + return ReturnData::create(ReturnData::SUCCESS,User::wxLogin(array('openid'=>$data['openid']))); + } + + //添加用户 + $res = User::wxRegister($data); + + if($res === false) + { + return ReturnData::create(ReturnData::SYSTEM_FAIL); + } + + return ReturnData::create(ReturnData::SUCCESS,User::wxLogin(array('openid'=>$data['openid']))); } //验证码登录 diff --git a/app/Http/Controllers/Weixin/UserController.php b/app/Http/Controllers/Weixin/UserController.php index ec76eb5..c5341c9 100644 --- a/app/Http/Controllers/Weixin/UserController.php +++ b/app/Http/Controllers/Weixin/UserController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Weixin; use App\Http\Controllers\Weixin\CommonController; use Illuminate\Http\Request; use App\Common\ReturnCode; +use App\Common\WechatAuth; class UserController extends CommonController { @@ -274,39 +275,58 @@ class UserController extends CommonController //微信网页授权登录 public function oauth(Request $request) { - if(isset($_SESSION['weixin_user_info'])) + $wechat_auth = new WechatAuth(sysconfig('CMS_WX_APPID'),sysconfig('CMS_WX_APPSECRET')); + + // 获取code码,用于和微信服务器申请token。 注:依据OAuth2.0要求,此处授权登录需要用户端操作 + if(!isset($_GET['code'])) { - if(isset($_SERVER["HTTP_REFERER"])){header('Location: '.$_SERVER["HTTP_REFERER"]);exit;} - header('Location: '.route('weixin_user'));exit; + $http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://'; + $callback_url = $http_type . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; //回调地址,当前页面 + //-------生成唯一随机串防CSRF攻击 + $state = md5(uniqid(rand(), true)); + $_SESSION['weixin_oauth']['state'] = $state; //存到SESSION + $authorize_url = $wechat_auth->get_authorize_url($callback_url, $state); + + header("Location: $authorize_url");exit; } - if($_SERVER['REQUEST_METHOD'] == 'POST') + // 依据code码去获取openid和access_token,自己的后台服务器直接向微信服务器申请即可 + if (isset($_GET['code'])) { - if($_POST['user_name'] == '') - { - $this->error_jump('账号不能为空'); - } + $_SESSION['weixin_oauth']['code'] = $_GET['code']; - if($_POST['password'] == '') + if($_GET['state'] != $_SESSION['weixin_oauth']['state']) { - $this->error_jump('密码不能为空'); + exit("您访问的页面不存在或已被删除!"); } - $postdata = array( - 'user_name' => $_POST['user_name'], - 'password' => md5($_POST['password']) - ); - $url = env('APP_API_URL')."/wx_login"; - $res = curl_request($url,$postdata,'POST'); - - if($res['code'] != ReturnCode::SUCCESS_CODE){$this->error_jump('登录失败');} - - $_SESSION['weixin_user_info'] = $res['data']; - - header('Location: '.route('weixin_user'));exit; + //得到 access_token 与 openid + $_SESSION['weixin_oauth']['token'] = $wechat_auth->get_access_token($_GET['code']); } - return view('weixin.user.login'); + // 依据申请到的access_token和openid,申请Userinfo信息。 + if (isset($_SESSION['weixin_oauth']['token'])) + { + $_SESSION['weixin_oauth']['userinfo'] = $wechat_auth->get_user_info($_SESSION['weixin_oauth']['token']['access_token'], $_SESSION['weixin_oauth']['token']['openid']); + } + + $postdata = array( + 'openid' => $_SESSION['weixin_oauth']['token']['openid'], + 'nickname' => $_SESSION['weixin_oauth']['userinfo']['nickname'], + 'sex' => $_SESSION['weixin_oauth']['userinfo']['sex'], + 'head_img' => $_SESSION['weixin_oauth']['userinfo']['headimgurl'], + 'parent_id' => '', + 'parent_mobile' => '', + 'mobile' => '' + ); + $url = env('APP_API_URL')."/wx_oauth_register"; + $res = curl_request($url,$postdata,'POST'); + dd($postdata); + if($res['code'] != ReturnCode::SUCCESS_CODE){$this->error_jump('系统错误');} + + $_SESSION['weixin_user_info'] = $res['data']; + + header('Location: '.route('weixin_user'));exit; } //登录 diff --git a/app/Http/Model/User.php b/app/Http/Model/User.php index 4e82032..0fa7081 100644 --- a/app/Http/Model/User.php +++ b/app/Http/Model/User.php @@ -180,13 +180,17 @@ class User extends BaseModel if(isset($mobile)){$data['mobile'] = $mobile;} if(isset($password)){$data['password'] = $password;} //md5加密 if(isset($parent_id)){$data['parent_id'] = $parent_id;} + if(isset($openid)){$data['openid'] = $openid} + if(isset($sex)){$data['sex'] = $sex} + if(isset($head_img)){$data['head_img'] = $head_img} + if(isset($nickname)){$data['nickname'] = $nickname} if (isset($data) && $id = self::add($data)) { //生成token return Token::getToken(Token::TYPE_WEIXIN, $id); } - + return false; } @@ -195,7 +199,7 @@ class User extends BaseModel { extract($param); //参数 - $user = self::where(array('mobile'=>$user_name,'password'=>$password))->orWhere(array('user_name'=>$user_name,'password'=>$password))->first(); + $user = self::where(array('mobile'=>$user_name,'password'=>$password))->orWhere(array('user_name'=>$user_name,'password'=>$password))->orWhere(array('openid'=>$openid))->first(); if(!$user){return false;} diff --git a/resources/org/wxJsSdk/jssdk.php b/resources/org/wxJsSdk/jssdk.php index 473567b..df19068 100644 --- a/resources/org/wxJsSdk/jssdk.php +++ b/resources/org/wxJsSdk/jssdk.php @@ -110,8 +110,8 @@ class JSSDK curl_setopt($curl, CURLOPT_TIMEOUT, 500); // 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。 // 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。 - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_URL, $url); $res = curl_exec($curl); diff --git a/resources/org/wxJsSdk/wx_sample.php b/resources/org/wxJsSdk/wx_sample.php new file mode 100644 index 0000000..bf33310 --- /dev/null +++ b/resources/org/wxJsSdk/wx_sample.php @@ -0,0 +1,89 @@ +valid();//使用-》访问类中valid方法,用来验证开发模式 +//11--23行代码为签名及接口验证。 +class wechatCallbackapiTest +{ + public function valid()//验证接口的方法 + { + $echoStr = $_GET["echostr"];//从微信用户端获取一个随机字符赋予变量echostr + + //valid signature , option访问地61行的checkSignature签名验证方法,如果签名一致,输出变量echostr,完整验证配置接口的操作 + if($this->checkSignature()){ + echo $echoStr; + exit; + } + } + //公有的responseMsg的方法,是我们回复微信的关键。以后的章节修改代码就是修改这个。 + public function responseMsg() + { + //get post data, May be due to the different environments + $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];//将用户端放松的数据保存到变量postStr中,由于微信端发送的都是xml,使用postStr无法解析,故使用$GLOBALS["HTTP_RAW_POST_DATA"]获取 + + //extract post data如果用户端数据不为空,执行30-55否则56-58 + if (!empty($postStr)){ + + $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);//将postStr变量进行解析并赋予变量postObj。simplexml_load_string()函数是php中一个解析XML的函数,SimpleXMLElement为新对象的类,LIBXML_NOCDATA表示将CDATA设置为文本节点,CDATA标签中的文本XML不进行解析 + $fromUsername = $postObj->FromUserName;//将微信用户端的用户名赋予变量FromUserName + $toUsername = $postObj->ToUserName;//将你的微信公众账号ID赋予变量ToUserName + $keyword = trim($postObj->Content);//将用户微信发来的文本内容去掉空格后赋予变量keyword + $time = time();//将系统时间赋予变量time + //构建XML格式的文本赋予变量textTpl,注意XML格式为微信内容固定格式,详见文档 + $textTpl = " + + + %s + + + 0 + "; + //39行,%s表示要转换成字符的数据类型,CDATA表示不转义 + //40行为微信来源方 + //41行为系统时间 + //42行为回复微信的信息类型 + //43行为回复微信的内容 + //44行为是否星标微信 + //XML格式文本结束符号 + if(!empty( $keyword ))//如果用户端微信发来的文本内容不为空,执行46--51否则52--53 + { + $msgType = "text";//回复文本信息类型为text型,变量类型为msgType + $contentStr = "Welcome to wechat world!";//我们进行文本输入的内容,变量名为contentStr,如果你要更改回复信息,就在这儿 + $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);//将XML格式中的变量分别赋值。注意sprintf函数 + echo $resultStr;//输出回复信息,即发送微信 + }else{ + echo "Input something...";//不发送到微信端,只是测试使用 + } + + }else { + echo "";//回复为空,无意义,调试用 + exit; + } + } + + //签名验证程序 ,checkSignature被18行调用。官方加密、校验流程:将token,timestamp,nonce这三个参数进行字典序排序,然后将这三个参数字符串拼接成一个字符串惊喜shal加密,开发者获得加密后的字符串可以与signature对比,表示该请求来源于微信。 + private function checkSignature() + { + $signature = $_GET["signature"];//从用户端获取签名赋予变量signature + $timestamp = $_GET["timestamp"];//从用户端获取时间戳赋予变量timestamp + $nonce = $_GET["nonce"]; //从用户端获取随机数赋予变量nonce + + $token = TOKEN;//将常量token赋予变量token + $tmpArr = array($token, $timestamp, $nonce);//简历数组变量tmpArr + sort($tmpArr, SORT_STRING);//新建排序 + $tmpStr = implode( $tmpArr );//字典排序 + $tmpStr = sha1( $tmpStr );//shal加密 + //tmpStr与signature值相同,返回真,否则返回假 + if( $tmpStr == $signature ){ + return true; + }else{ + return false; + } + } +} +?> \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 597af56..96be818 100644 --- a/routes/web.php +++ b/routes/web.php @@ -52,7 +52,7 @@ Route::group(['namespace' => 'Home'], function () { }); -//微信路由 +//微信路由,无需登录 Route::group(['prefix' => 'weixin', 'namespace' => 'Weixin'], function () { Route::get('/', 'IndexController@index')->name('weixin'); Route::get('/page404', 'IndexController@page404')->name('weixin_page404'); //404页面 @@ -64,6 +64,7 @@ Route::group(['prefix' => 'weixin', 'namespace' => 'Weixin'], function () { Route::get('/goods/{id}', 'GoodsController@goodsDetail')->name('weixin_goods_detail'); //商品详情页 Route::get('/goodslist', 'GoodsController@goodsList')->name('weixin_goods_list'); //产品分类页 + Route::any('/wxoauth', 'UserController@oauth')->name('weixin_wxoauth'); //微信网页授权 Route::any('/login', 'UserController@login')->name('weixin_login'); Route::any('/register', 'UserController@register')->name('weixin_register'); Route::get('/logout', 'UserController@logout')->name('weixin_user_logout'); //退出 @@ -130,6 +131,7 @@ Route::group(['prefix' => 'dataapi', 'namespace' => 'Api', 'middleware' => ['web //用户 Route::post('/wx_register', 'UserController@wxRegister'); //注册 Route::post('/wx_login', 'UserController@wxLogin'); //登录 + Route::post('/wx_oauth_register', 'UserController@wxOauthRegister'); //微信授权注册登录 }); //API接口路由,需token验证