Passed
Push — master ( ffbf7e...4ee940 )
by ma
01:42
created

Wechat::descryptData()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
dl 0
loc 25
rs 9.4888
c 1
b 0
f 0
cc 5
nc 5
nop 3
1
<?php
2
/**
3
 * 网站应用微信登录开发 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=
4
 * 1.PC登录:微信开放平台创建'网站应用'
5
 * 2.Mobile登录:微信公众号(服务号/企业号)
6
 * 3.APP登录:微信开放平台创建'移动应用'
7
 *
8
 * 注: scope值
9
 *      1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
10
 *      2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。(H5页面微信授权获取用户,注册成为用户id,可以做点赞关注等功能)
11
 *      3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
12
 * 如想打通unionid的话需要将公众号绑定到同一个微信开放平台
13
 */
14
namespace tinymeng\OAuth2\Gateways;
15
16
use tinymeng\OAuth2\Connector\Gateway;
17
use tinymeng\OAuth2\Helper\ConstCode;
18
19
/**
20
 * Class Wechat
21
 * @package tinymeng\OAuth2\Gateways
22
 * @Author: TinyMeng <[email protected]>
23
 * @Created: 2018/11/9
24
 */
25
class Wechat extends Gateway
26
{
27
    const API_BASE            = 'https://api.weixin.qq.com/sns/';
28
    protected $AuthorizeURL   = 'https://open.weixin.qq.com/connect/qrconnect';
29
    protected $AccessTokenURL = 'https://api.weixin.qq.com/sns/oauth2/access_token';
30
    protected $jsCode2Session = 'https://api.weixin.qq.com/sns/jscode2session';
31
32
    /**
33
     * Description:  得到跳转地址
34
     * @author: JiaMeng <[email protected]>
35
     * Updater:
36
     * @return string
37
     */
38
    public function getRedirectUrl()
39
    {
40
        //存储state
41
        $this->saveState();
42
43
        //获取代理链接
44
        if(isset($this->config['proxy_url'])){
45
            return $this->getProxyURL();
46
        }
47
48
        //登录参数
49
        $this->switchAccessTokenURL();
50
        $params = [
51
            'appid'         => $this->config['app_id'],
52
            'redirect_uri'  => $this->config['callback'],
53
            'response_type' => $this->config['response_type'],
54
            'scope'         => $this->config['scope'],
55
            'state'         => $this->config['state'],
56
        ];
57
        return $this->AuthorizeURL . '?' . http_build_query($params) . '#wechat_redirect';
58
    }
59
60
    /**
61
     * Description:  获取中转代理地址
62
     * @author: JiaMeng <[email protected]>
63
     * Updater:
64
     * @return string
65
     */
66
    public function getProxyURL()
67
    {
68
        $params = [
69
            'appid'         => $this->config['app_id'],
70
            'response_type' => $this->config['response_type'],
71
            'scope'         => $this->config['scope'],
72
            'state'         => $this->config['state'],
73
            'redirect_uri'    => $this->config['callback'],
74
        ];
75
        return $this->config['proxy_url'] . '?' . http_build_query($params);
76
    }
77
78
    /**
79
     * Description:  获取当前授权用户的openid标识
80
     * @author: JiaMeng <[email protected]>
81
     * Updater:
82
     * @return mixed
83
     * @throws \Exception
84
     */
85
    public function openid()
86
    {
87
        $this->getToken();
88
89
        if (isset($this->token['openid'])) {
90
            return $this->token['openid'];
91
        } else {
92
            throw new \Exception('没有获取到微信用户ID!');
93
        }
94
    }
95
96
    /**
97
     * Description:  获取格式化后的用户信息
98
     * @return array
99
     * @throws \Exception
100
     * @author: JiaMeng <[email protected]>
101
     * Updater:
102
     */
103
    public function userInfo()
104
    {
105
        $result = $this->getUserInfo();
106
107
        $userInfo = [
108
            'open_id' => $this->openid(),
109
            'union_id'=> $this->token['unionid'] ?? '',
110
            'access_token'=> $this->token['access_token'] ?? '',
111
            'channel' => ConstCode::TYPE_WECHAT,
112
            'nickname'=> $result['nickname']??'',
113
            'gender'  => $result['sex'] ?? ConstCode::GENDER,
114
            'avatar'  => $result['headimgurl']??'',
115
            'session_key'  => $result['session_key']??'',
116
        ];
117
        $userInfo['type'] = ConstCode::getTypeConst($userInfo['channel'],$this->type);
118
        return $userInfo;
119
    }
120
121
    /**
122
     * Description:  获取原始接口返回的用户信息
123
     * @return array
124
     * @throws \Exception
125
     * @author: JiaMeng <[email protected]>
126
     * Updater:
127
     */
128
    public function getUserInfo()
129
    {
130
        if($this->type == 'app'){//App登录
131
            if(!isset($_REQUEST['access_token']) ){
132
                throw new \Exception("Wechat APP登录 需要传输access_token参数! ");
133
            }
134
            $this->token['access_token'] = $_REQUEST['access_token'];
135
        }elseif ($this->type == 'applets'){
136
            //小程序
137
            return $this->applets();
138
        }else {
139
            /** 获取token信息 */
140
            $this->getToken();
141
        }
142
143
        /** 获取用户信息 */
144
        $params = [
145
            'access_token'=>$this->token['access_token'],
146
            'openid'=>$this->openid(),
147
            'lang'=>'zh_CN',
148
        ];
149
        $data = $this->get(self::API_BASE . 'userinfo', $params);
150
        return json_decode($data, true);
151
    }
152
153
    /**
154
     * @return array|mixed|null
155
     * @throws \Exception
156
     */
157
    public function applets(){
158
        /** 获取参数 */
159
        $params = $this->accessTokenParams();
160
        $params['js_code'] = $params['code'];
161
162
        /** 获取access_token */
163
        $token =  $this->get($this->jsCode2Session, $params);
164
        /** 解析token值(子类实现此方法) */
165
        $this->token = $this->parseToken($token);
166
        return $this->token;
167
    }
168
169
    /**
170
     * Description:  根据第三方授权页面样式切换跳转地址
171
     * @author: JiaMeng <[email protected]>
172
     * Updater:
173
     */
174
    private function switchAccessTokenURL()
175
    {
176
        /**
177
         *  第三方使用网站应用授权登录前请注意已获取相应网页授权作用域
178
         *  Pc网站应用 https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
179
         *  微信内网站应用: https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=SCOPE&state=1#wechat_redirect
180
         */
181
        if ($this->display == 'mobile') {
182
            $this->AuthorizeURL = 'https://open.weixin.qq.com/connect/oauth2/authorize';
183
        } else {
184
            //微信扫码网页登录,只支持此scope
185
            $this->config['scope'] = 'snsapi_login';
186
        }
187
    }
188
189
    /**
190
     * Description:  重写 获取的AccessToken请求参数
191
     * @author: JiaMeng <[email protected]>
192
     * Updater:
193
     * @return array
194
     */
195
    protected function accessTokenParams()
196
    {
197
        $params = [
198
            'appid'      => $this->config['app_id'],
199
            'secret'     => $this->config['app_secret'],
200
            'grant_type' => $this->config['grant_type'],
201
            'code'       => $this->getCode(),
202
        ];
203
        return $params;
204
    }
205
206
    /**
207
     * Description:  解析access_token方法请求后的返回值
208
     * @author: JiaMeng <[email protected]>
209
     * Updater:
210
     * @param string $token 获取access_token的方法的返回值
211
     * @return mixed
212
     * @throws \Exception
213
     */
214
    protected function parseToken($token)
215
    {
216
        $data = json_decode($token, true);
217
        if (isset($data['access_token'])) {
218
            return $data;
219
        }elseif (isset($data['session_key'])){
220
            //小程序登录
221
            return $data;
222
        } else {
223
            throw new \Exception("获取微信 ACCESS_TOKEN 出错:{$token}");
224
        }
225
    }
226
227
    /**
228
     * 解密小程序 wx.getUserInfo() 敏感数据.
229
     * @param string $encryptedData
230
     * @param string $iv
231
     * @param string $sessionKey
232
     * @return array
233
     */
234
    public function descryptData($encryptedData, $iv, $sessionKey)
235
    {
236
        if (24 != strlen($sessionKey))
237
        {
238
            throw new \InvalidArgumentException('sessionKey 格式错误');
239
        }
240
        if (24 != strlen($iv))
241
        {
242
            throw new \InvalidArgumentException('iv 格式错误');
243
        }
244
        $aesKey = base64_decode($sessionKey);
245
        $aesIV = base64_decode($iv);
246
        $aesCipher = base64_decode($encryptedData);
247
        $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV);
248
        if (!$result)
249
        {
250
            throw new \InvalidArgumentException('解密失败');
251
        }
252
        $dataObj = json_decode($result, true);
253
        if (!$dataObj)
254
        {
255
            throw new \InvalidArgumentException('反序列化数据失败');
256
        }
257
258
        return $dataObj;
259
    }
260
261
}