├── .gitignore
├── .htaccess
├── README.md
├── app
├── app.php
├── config
│ ├── common.php
│ ├── config.php
│ ├── loader.php
│ └── services.php
├── services
│ ├── JwtAuth.php
│ ├── Wechat.php
│ └── WechatAuth.php
└── views
│ ├── 404.phtml
│ ├── auth-login-error.phtml
│ ├── auth-login.phtml
│ ├── auth-login2.phtml
│ ├── auth-redirect-demo.phtml
│ ├── index.phtml
│ └── qrcode-login-demo.phtml
├── composer.json
├── index.html
└── public
├── .htaccess
├── img
└── login-logo.png
└── index.php
/.gitignore:
--------------------------------------------------------------------------------
1 | log/
2 | cache/
3 | .idea/
4 | vendor/
5 | composer.lock
6 | resource/
7 | .DS_Store
8 | app/.DS_Store
9 | Runtime/
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 |
22 | ```bash
23 | 设计思路:
24 | 生成一张待authKey参数的二维码,该参数随机且不重复
25 | 用户扫描二维码,先跳转到微信授权页面授权,同时把授权获得信息存入缓存,该缓存带有状态
26 | 用户点击确认登录,修改缓存状态为1,取消则为2 无操作默认0
27 | 网站通过计划任务向指定接口获取到对应key值的缓存数据(该缓存数据通过jwt加密成token),
28 | 获取到对应的用户信息的token以后跳转到预设好的回调地址,同时在url带上wechatToken参数,该参数需要在服务端解密
29 | ```
30 |
31 |
32 | 2、[跳转重定向登录](https://wechat.zhoujianjun.cn/redirectDemo)
33 | ```bash
34 | 设计思路:
35 | 网站跳转到指定的授权回调地址同时带上redirectUrl的参数,该参数为回调参数
36 | 授权回调地址通过微信授权以后直接把对应的数据加密成token拼装到redirectUrl上去
37 | 该参数需要在服务端解密
38 | ```
39 |
40 | 备注:为什么一定要加密,就是为了防止用户的数据通过缓存以后被劫持篡改,所以通过jwt加密以后提交数据的安全性,但是这也导致url链接变长
41 |
42 | 依赖包
43 | -------
44 |
45 | 1、[PHP-JWT token认证](https://github.com/firebase/php-jwt):集成JWT认证类,已封装加密解密的类方法
46 |
47 | 2、[微信SDK](https://github.com/thenbsp/wechat):微信的大部分常用的SDK都已封装,可查看WIKI文档
48 |
49 | 2、[二维码生成类](https://github.com/SimpleSoftwareIO/simple-qrcode):常用的二维码生成类包
50 |
51 |
52 |
53 | 微型项目结构
54 | -------
55 |
56 | ```bash
57 | project/
58 | app/
59 | config/ ---配置文件
60 | services/ ---业务类(存放业务操作方法)
61 | views/ ---视图
62 | public/ ---公共资源
63 | css/
64 | img/
65 | js/
66 | cache/ ---缓存文件(缓存,视图)
67 | vendor/ ---composer包管理
68 | ```
69 |
70 |
71 | 感言
72 | -------
73 |
74 | 1、命名空间是个大坑,写方法的时候一定要注意命名空间的使用,一不小心就坑的你吐。
75 |
76 | 2、不要重复造轮子,多去找找有没有composer包,[点击传送门](https://packagist.org/)
77 |
78 | 3、多查看手册 [官方英文手册](https://docs.phalconphp.com/en/3.2) [3.0的中文手册](http://www.iphalcon.cn/)
79 |
80 | 4、记住多看手册,基本上大部分遇到的坑都会在手册查看,类的用法可以多查API [点击传送门](https://docs.phalconphp.com/en/3.2/api/index)
81 |
82 |
83 |
84 | 加入我们
85 | -------
86 | 交流群:150237524
87 |
88 | 我的QQ:773729704 记得备注github
89 |
90 | 我的微信:huoniaojugege 记得备注github
91 |
--------------------------------------------------------------------------------
/app/app.php:
--------------------------------------------------------------------------------
1 | 300]), ["cacheDir" => BASE_PATH . "/cache/"]);
18 |
19 |
20 | function responseData($code = 1, $msg = 'success', $data = [])
21 | {
22 | if (empty($data) || (!is_array($data) && !is_object($data))) {
23 | $data = new stdClass();
24 | }
25 | $response = \Phalcon\Di::getDefault()->getResponse();
26 | $response->setJsonContent(compact('code', 'msg', 'data'));
27 | $response->send();
28 | die;
29 | }
30 |
31 |
32 | /**
33 | * Add your routes here
34 | */
35 | $app->get('/', function () {
36 | echo $this['view']->render('index');
37 | });
38 |
39 | /**
40 | * TODO =========================== 微信扫码登录 ============================================
41 | */
42 |
43 | /**
44 | * 获取登录二维码
45 | */
46 | /**
47 | * 微信扫码授权
48 | * 授权数据存入缓存
49 | * 同时跳转页面到是否同意登录申请,同意则登录,不同意则取消
50 | */
51 | $app->get('/getQrcode', function () {
52 | $key = !empty($_GET['authKey']) ? $_GET['authKey'] : 123;
53 | $qrcode = new SimpleSoftwareIO\QrCode\BaconQrCodeGenerator();
54 | $qrcodeEn = $qrcode->size(300)->generate('https://wechat.zhoujianjun.cn/authLogin?timestamp=' . (time() + 300) . '&authKey=' . $key);
55 | echo $qrcodeEn;
56 | });
57 |
58 |
59 | /**
60 | * 微信扫码授权
61 | * 授权数据存入缓存
62 | * 同时跳转页面到是否同意登录申请,同意则登录,不同意则取消
63 | */
64 | $app->get('/authLogin', function () use ($cache) {
65 | $key = !empty($_GET['authKey']) ? $_GET['authKey'] : '';
66 | $timestamp = !empty($_GET['timestamp']) ? $_GET['timestamp'] : '';
67 | if (empty($key)) {
68 | $this['view']->error = '授权KEY获取失败';
69 | exit($this['view']->render('auth-login-error'));
70 | }
71 | if ($timestamp < time()) {
72 | $this['view']->error = '该二维码已失效,请重新二维码';
73 | exit($this['view']->render('auth-login-error'));
74 | }
75 | $wechatInfo = $cache->get($key);
76 | if (!$wechatInfo) {
77 | $wechat = new Services\WechatAuth();
78 | $wechatInfo = ['userInfo' => $wechat->auth(), 'status' => 0];
79 | $cache->save($key, $wechatInfo);
80 | } else {
81 | if ($wechatInfo['status'] == 1) {
82 | $this['view']->error = '该二维码已失效,请重新二维码';
83 | exit($this['view']->render('auth-login-error'));
84 | }
85 | }
86 | $this->session->set('openid', $wechatInfo['userInfo']->openid);
87 | $this['view']->authKey = $key;
88 | echo $this['view']->render('auth-login2');
89 | });
90 |
91 | /**
92 | * 设置缓存授权状态
93 | */
94 | $app->post('/setAuth', function () use ($cache) {
95 | $key = !empty($_POST['authKey']) ? $_POST['authKey'] : '';
96 | $status = !empty($_POST['status']) ? $_POST['status'] : '';
97 | if (empty($key)) {
98 | responseData(-1, '授权key不能为空');
99 | }
100 | $authCache = $cache->get($key);
101 | if (!$authCache) {
102 | responseData(-100, '该用户未微信授权,请重新授权登录');
103 | }
104 | if ($this->session->get('openid') != $authCache['userInfo']->openid) {
105 | responseData(-1, '不可操作其他用户的数据');
106 | }
107 | $authCache['status'] = $status ? 1 : 2;
108 | $flag = $cache->save($key, $authCache);
109 | if ($flag) {
110 | responseData(1, $status?'授权成功':'已取消');
111 | } else {
112 | responseData(-1, '授权失败');
113 | }
114 | });
115 |
116 | /**
117 | * 获取认证授权状态
118 | * 如果用户已授权则返回对应的缓存数据
119 | */
120 | $app->map('/getAuth', function () use ($cache) {
121 | $request = new \Phalcon\Http\Request();
122 | if($request->isOptions()){
123 | responseData(-1, '快速返回跨域信息');
124 | }
125 | $key = !empty($_POST['authKey']) ? $_POST['authKey'] : '';
126 | $key = !empty($_GET['authKey'])?$_GET['authKey']:$key;
127 | if (empty($key)) {
128 | responseData(-1, '授权key不能为空');
129 | }
130 | $authCache = $cache->get($key);
131 | if (!$authCache || empty($authCache['status'])) {
132 | responseData(-2, '该用户未微信授权,请重新授权登录');
133 | }
134 | if ($authCache['status'] == 2) {
135 | responseData(-5, '该用户已取消登录');
136 | }
137 | $token = \Services\JwtAuth::type()->encode($authCache['userInfo']);
138 | responseData(1, '授权成功', compact('token'));
139 | });
140 |
141 |
142 | /**
143 | * TODO =========================== 微信公众号重定向授权登录 ============================================
144 | */
145 |
146 | /**
147 | * 微信web公众号授权
148 | * 授权获取用户信息,直接跳转到改用户设置的回调地址上同时带上用户信息
149 | */
150 | $app->get('/authWeb', function () use ($cache) {
151 | $url = !empty($_GET['redirectUrl']) ? $_GET['redirectUrl'] : '';
152 | if (empty($url)) {
153 | $this['view']->error = '回调地址异常';
154 | exit($this['view']->render('auth-login-error'));
155 | }
156 | $wechat = new Services\WechatAuth();
157 | $userInfo = $wechat->auth();
158 | $userInfo->expire_time = time() + 60;
159 | $checkParam = strpos($url, '?');
160 | if ($checkParam) {
161 | $url = $url . '&wechatToken=' . \Services\JwtAuth::type()->encode($userInfo);
162 | } else {
163 | $url = $url . '?wechatToken=' . \Services\JwtAuth::type()->encode($userInfo);
164 | }
165 | header("Location:" . $url);
166 | });
167 |
168 |
169 | /**
170 | * 微信web公众号授权
171 | * 授权获取用户信息,直接跳转到改用户设置的回调地址上同时带上用户信息
172 | */
173 | $app->get('/getUser', function () use ($cache) {
174 | $userI = \Services\JwtAuth::type()->decode($_GET['wechatToken']);
175 | var_dump($userI, 111111);
176 | });
177 |
178 |
179 | /**
180 | * 二维码登录demo
181 | */
182 | $app->get('/qrcodeDemo',function (){
183 | echo $this['view']->render('qrcode-login-demo');
184 | });
185 |
186 | /**
187 | * 重定向登录demo
188 | */
189 | $app->get('/redirectDemo',function (){
190 | echo $this['view']->render('auth-redirect-demo');
191 | });
192 |
193 | /**
194 | * Not found handler
195 | */
196 | $app->notFound(function () use ($app) {
197 | $app->response->setStatusCode(404, "Not Found")->sendHeaders();
198 | echo $app['view']->render('404');
199 | });
200 |
--------------------------------------------------------------------------------
/app/config/common.php:
--------------------------------------------------------------------------------
1 | [
15 | 'app_id' => 'wx3f37b98cf00ee980',
16 | 'secret' => '31f38c6df982f1be471df244bdeb5cfe',
17 | ],
18 |
19 | /**
20 | * 签名信息配置信息
21 | */
22 | 'api_sign' => [
23 | 'key' => '',
24 | 'status' => true,
25 | 'expire_time' => 120,
26 | ],
27 | /**
28 | * 七牛上传配置信息
29 | */
30 | 'qiniu' => [
31 | 'accessKey' => '',
32 | 'secretKey' => '',
33 | 'bucket' => '',
34 | 'url' => '',
35 | ],
36 | /**
37 | * Jpush极光推送配置
38 | */
39 | 'jpush' => [
40 | 'default' => [
41 | 'app_key' => '',
42 | 'master_secret' => '',
43 | 'production' => false,
44 | ]
45 | ],
46 | /**
47 | * 微信支付配置信息
48 | */
49 | 'wechat_pay' => [
50 | 'app' => [
51 | 'app_id' => '',
52 | 'app_secret' => '',
53 | 'mch_id' => '',
54 | 'md5_key' => '',
55 | 'cert_pem' => '',
56 | 'key_pem' => '',
57 | ],
58 | 'web' => [
59 | 'app_id' => '',
60 | 'app_secret' => '',
61 | 'mch_id' => '',
62 | 'md5_key' => '',
63 | 'cert_pem' => BASE_PATH.'/resource/wechat/apiclient_cert.pem',
64 | 'key_pem' => BASE_PATH.'/resource/wechat/apiclient_key.pem',
65 | ]
66 | ],
67 | /**
68 | * 支付宝支付
69 | */
70 | 'alipay' => [
71 | 'app_id' => '',
72 | 'partner' => '',
73 | 'seller_id'=>'',
74 | 'ali_public_key' => BASE_PATH.'/resource/alipay/alipay_public_key.pem',
75 | 'rsa_private_key' => BASE_PATH.'/resource/alipay/rsa_private_key.pem',
76 | ],
77 | /**
78 | * JwtAuth token授权配置
79 | */
80 | 'jwt_auth'=>[
81 | 'type'=>'HS256',
82 | 'key'=>'zhouxiansheng',
83 | 'privete'=>BASE_PATH.'/resource/jwtauth/id_ras',
84 | 'public'=>BASE_PATH.'/resource/jwtauth/id_ras.pub',
85 | ],
86 | ]);
--------------------------------------------------------------------------------
/app/config/config.php:
--------------------------------------------------------------------------------
1 | [
11 | 'adapter' => 'Mysql',
12 | 'host' => 'localhost',
13 | 'username' => 'root',
14 | 'password' => '',
15 | 'dbname' => 'test',
16 | 'charset' => 'utf8',
17 | ],
18 |
19 | 'application' => [
20 | 'viewsDir' => APP_PATH . '/views/',
21 | 'baseUri' => '/wechat-auth/',
22 | ]
23 | ]);
24 |
--------------------------------------------------------------------------------
/app/config/loader.php:
--------------------------------------------------------------------------------
1 | registerDirs(
9 | [
10 | $config->application->viewsDir
11 | ]
12 | )->register();
13 |
14 |
15 | /**
16 | * 注册命名空间
17 | */
18 | $loader->registerNamespaces([
19 | 'Services' => '../app/services',
20 | ])->register();
21 |
--------------------------------------------------------------------------------
/app/config/services.php:
--------------------------------------------------------------------------------
1 | setShared(
8 | "session",
9 | function () {
10 | $session = new Session();
11 | $session->start();
12 | return $session;
13 | }
14 | );
15 |
16 | /**
17 | * Shared configuration service
18 | */
19 | $di->setShared('config', function () {
20 | return include APP_PATH . "/config/config.php";
21 | });
22 |
23 | /**
24 | * Shared configuration service
25 | */
26 | $di->setShared('commonConfig', function () {
27 | return include APP_PATH . "/config/common.php";
28 | });
29 |
30 | /**
31 | * Sets the view component
32 | */
33 | $di->setShared('view', function () {
34 | $config = $this->getConfig();
35 |
36 | $view = new View();
37 | $view->setViewsDir($config->application->viewsDir);
38 | return $view;
39 | });
40 |
41 | /**
42 | * The URL component is used to generate all kind of urls in the application
43 | */
44 | $di->setShared('url', function () {
45 | $config = $this->getConfig();
46 |
47 | $url = new UrlResolver();
48 | $url->setBaseUri($config->application->baseUri);
49 | return $url;
50 | });
51 |
52 | /**
53 | * Database connection is created based in the parameters defined in the configuration file
54 | */
55 | $di->setShared('db', function () {
56 | $config = $this->getConfig();
57 |
58 | $class = 'Phalcon\Db\Adapter\Pdo\\' . $config->database->adapter;
59 | $params = [
60 | 'host' => $config->database->host,
61 | 'username' => $config->database->username,
62 | 'password' => $config->database->password,
63 | 'dbname' => $config->database->dbname,
64 | 'charset' => $config->database->charset
65 | ];
66 |
67 | if ($config->database->adapter == 'Postgresql') {
68 | unset($params['charset']);
69 | }
70 |
71 | $connection = new $class($params);
72 |
73 | return $connection;
74 | });
75 |
76 |
--------------------------------------------------------------------------------
/app/services/JwtAuth.php:
--------------------------------------------------------------------------------
1 | get("commonConfig")->jwt_auth;
30 |
31 | /** 优先获取用户设置 */
32 | if (!$type) {
33 | if ($jwt_auth_config->type == 'RS256') {
34 | self::$privateKey = file_get_contents($jwt_auth_config->privete);
35 | self::$publicKey = file_get_contents($jwt_auth_config->public);
36 | } else {
37 | self::$jwtKey = $jwt_auth_config->key;
38 | }
39 | self::$type = $jwt_auth_config->type;
40 | } else {
41 | if ($type == 'RS256') {
42 | self::$privateKey = file_get_contents($jwt_auth_config->privete);
43 | self::$publicKey = file_get_contents($jwt_auth_config->public);
44 | } else {
45 | self::$jwtKey = $jwt_auth_config->key;
46 | }
47 | self::$type = $type;
48 | }
49 | return new self();
50 | }
51 |
52 | /**
53 | * 生成token字符串
54 | * @param $data
55 | * @return string
56 | */
57 | public function encode($data = array())
58 | {
59 | if (self::$type == 'RS256') {
60 | $tokenStr = JWT::encode($data, self::$privateKey, self::$type);
61 | } else {
62 | $tokenStr = JWT::encode($data, self::$jwtKey);
63 | }
64 | return $tokenStr;
65 |
66 | }
67 |
68 | /**
69 | * 验证token字符串
70 | * 返回数组
71 | * @param string $jwt
72 | * @return object
73 | */
74 | public function decode($jwt = '')
75 | {
76 | try {
77 | if (self::$type == 'RS256') {
78 | $data = JWT::decode($jwt, self::$publicKey, array(self::$type));
79 | } else {
80 | $data = JWT::decode($jwt, self::$jwtKey, array(self::$type));
81 | }
82 | if(!is_array($data) && !is_object($data)){
83 | throw new Exception('授权认证失败');
84 | }else{
85 | //logMessage('jwt-auth')->log('授权成功----->'.json_encode($data));
86 | return $data;
87 | }
88 | }catch (Exception $e){
89 | //logMessage('jwt-auth')->error('授权失败----->'.$jwt);
90 | return false;
91 | }
92 | }
93 |
94 |
95 | }
--------------------------------------------------------------------------------
/app/services/Wechat.php:
--------------------------------------------------------------------------------
1 | config = \Phalcon\Di::getDefault()->get('wechat_config');
24 | }
25 | /**
26 | * 微信授权登录
27 | */
28 | public function auth($key,$url)
29 | {
30 | $client = new Client($this->config->auth->app_id, $this->config->auth->secret);
31 | $client->setScope('snsapi_userinfo');
32 | $client->setRedirectUri('https://'.$_SERVER['HTTP_HOST'].'/redirect');
33 |
34 | if (!isset($_GET['code'])) {
35 | header('Location: ' . $client->getAuthorizeUrl());
36 | }
37 | $accessToken = $client->getAccessToken($_GET['code']);
38 | $userinfo = $accessToken->getUser()->toArray();
39 |
40 | $cache = new BackFile(new FrontData(["lifetime" => 120]), ["cacheDir" => BASE_PATH."/cache/"]);
41 | $cache->save($key, ['redirectUrl'=>$url,'userInfo'=>$userinfo,'accessToken'=>$accessToken->toArray()]);
42 | }
43 |
44 | /**
45 | * 获取授权缓存信息
46 | * @param $key
47 | * @return mixed
48 | */
49 | public function getAuth($key)
50 | {
51 | $cache = new BackFile(new FrontData(["lifetime" => 120]), ["cacheDir" => BASE_PATH."/cache/"]);
52 | return $cache->get($key);
53 | }
54 | }
--------------------------------------------------------------------------------
/app/services/WechatAuth.php:
--------------------------------------------------------------------------------
1 | get("commonConfig")->wechat_auth;
20 | $this->appId = $config->app_id;
21 | $this->appSecret = $config->secret;
22 | }
23 |
24 | public function auth()
25 | {
26 | $res = $this->GetOpenid();
27 | $url = "https://api.weixin.qq.com/sns/userinfo?access_token=" . $res['access_token'] . "&openid=" . $res['openid'] . "&lang=zh_CN";
28 | $ret = json_decode(file_get_contents($url));
29 | $ret->unionid = isset($res['unionid'])?$res['unionid']:'';
30 | return $ret;
31 | }
32 |
33 | public function GetOpenid()
34 | {
35 | //通过code获得openid
36 | if (!isset($_GET['code'])) {
37 | //触发微信返回code码
38 | $baseUrl = urlencode('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
39 | $url = $this->__CreateOauthUrlForCode($baseUrl);
40 | Header("Location: $url");
41 | exit();
42 | } else {
43 | //获取code码,以获取openid
44 | $code = $_GET['code'];
45 | return $this->getOpenidFromMp($code);
46 | }
47 | }
48 |
49 | private function __CreateOauthUrlForCode($redirectUrl)
50 | {
51 | $urlObj["appid"] = $this->appId;
52 | $urlObj["redirect_uri"] = "$redirectUrl";
53 | $urlObj["response_type"] = "code";
54 | $urlObj["scope"] = "snsapi_userinfo";
55 | $urlObj["state"] = "STATE" . "#wechat_redirect";
56 | $bizString = $this->ToUrlParams($urlObj);
57 | return "https://open.weixin.qq.com/connect/oauth2/authorize?" . $bizString;
58 | }
59 |
60 | public function GetOpenidFromMp($code)
61 | {
62 | $url = $this->__CreateOauthUrlForOpenid($code);
63 | $ch = curl_init();
64 | curl_setopt($ch, CURLOPT_TIMEOUT, 10);
65 | curl_setopt($ch, CURLOPT_URL, $url);
66 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
67 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
68 | curl_setopt($ch, CURLOPT_HEADER, FALSE);
69 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
70 | //运行curl,结果以jason形式返回
71 | $res = curl_exec($ch);
72 | curl_close($ch);
73 | return json_decode($res, true);
74 | }
75 |
76 | private function __CreateOauthUrlForOpenid($code)
77 | {
78 | $urlObj["appid"] = $this->appId;
79 | $urlObj["secret"] = $this->appSecret;
80 | $urlObj["code"] = $code;
81 | $urlObj["grant_type"] = "authorization_code";
82 | $bizString = $this->ToUrlParams($urlObj);
83 | return "https://api.weixin.qq.com/sns/oauth2/access_token?" . $bizString;
84 | }
85 |
86 | private function ToUrlParams($urlObj)
87 | {
88 | $buff = "";
89 | foreach ($urlObj as $k => $v) {
90 | if ($k != "sign") {
91 | $buff .= $k . "=" . $v . "&";
92 | }
93 | }
94 | $buff = trim($buff, "&");
95 | return $buff;
96 | }
97 | }
--------------------------------------------------------------------------------
/app/views/404.phtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
重定向跳转
17 |You're now flying with Phalcon. Great things are about to happen!
19 | 20 |This page is located at views/index.phtml
二维码扫描跳转
17 |Please enable rewrite module on your web server to continue
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 | AddDefaultCharset UTF-8
2 |
3 |
';
57 | echo '
' . $e->getTraceAsString() . ''; 58 | } 59 | --------------------------------------------------------------------------------