majiameng /
OAuth2
| 1 | <?php |
||||
| 2 | /** |
||||
| 3 | * 蚂蚁金服 https://openhome.alipay.com |
||||
| 4 | * 支付宝第三方应用授权文档 |
||||
| 5 | * https://docs.open.alipay.com/20160728150111277227 |
||||
| 6 | * 1.设置:回调地址/加签方式(RSA(SHA256)密钥)/接口内容加密方式(AES密钥) |
||||
| 7 | * 应用公钥(SHA256withRsa)生成方法 |
||||
| 8 | * 1.1 在liunx 使用 openssl |
||||
| 9 | * 1.2 OpenSSL> genrsa -out rsa_private_key.pem 2048 #生成私钥 |
||||
| 10 | * 1.3 OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式 |
||||
| 11 | * 1.4 OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥 |
||||
| 12 | * 1.5 OpenSSL> exit #退出OpenSSL程序 |
||||
| 13 | * 1.6 复制rsa_public_key.pem 中间那一段到支付宝填写(注: 不要复制头和尾) |
||||
| 14 | * 2.1.PC登录需要签约: 第三方应用授权/获取会员信息 |
||||
| 15 | * 2.2.APP登录需要签约: APP支付宝登录/获取会员信息 |
||||
| 16 | * |
||||
| 17 | * 网页授权文档:https://opendocs.alipay.com/open/284/web |
||||
| 18 | */ |
||||
| 19 | namespace tinymeng\OAuth2\Gateways; |
||||
| 20 | |||||
| 21 | use tinymeng\OAuth2\Connector\Gateway; |
||||
| 22 | use tinymeng\OAuth2\Exception\OAuthException; |
||||
| 23 | use tinymeng\OAuth2\Helper\ConstCode; |
||||
| 24 | use tinymeng\OAuth2\Helper\Str; |
||||
| 25 | |||||
| 26 | /** |
||||
| 27 | * Class Alipay |
||||
| 28 | * @package tinymeng\OAuth2\Gateways |
||||
| 29 | * @Author: TinyMeng <[email protected]> |
||||
| 30 | * @Created: 2018/11/9 |
||||
| 31 | */ |
||||
| 32 | class Alipay extends Gateway |
||||
| 33 | { |
||||
| 34 | const RSA_PRIVATE = 1; |
||||
| 35 | const RSA_PUBLIC = 2; |
||||
| 36 | |||||
| 37 | const API_BASE = 'https://openapi.alipay.com/gateway.do'; |
||||
| 38 | protected $AuthorizeURL = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm'; |
||||
| 39 | |||||
| 40 | /** |
||||
| 41 | * 非必须参数。接口权限值,目前只支持 auth_user 和 auth_base 两个值。以空格分隔的权限列表,若不传递此参数,代表请求的数据访问操作权限与上次获取Access Token时一致。通过Refresh Token刷新Access Token时所要求的scope权限范围必须小于等于上次获取Access Token时授予的权限范围。 |
||||
| 42 | * @var string |
||||
| 43 | */ |
||||
| 44 | protected $scope; |
||||
| 45 | |||||
| 46 | /** |
||||
| 47 | * 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2. |
||||
| 48 | * @var string |
||||
| 49 | */ |
||||
| 50 | protected $sign_type = 'RSA2'; |
||||
| 51 | |||||
| 52 | /** |
||||
| 53 | * @param $config |
||||
| 54 | */ |
||||
| 55 | public function __construct($config) |
||||
| 56 | { |
||||
| 57 | parent::__construct($config); |
||||
| 58 | $this->AccessTokenURL = static::API_BASE; |
||||
| 59 | } |
||||
| 60 | |||||
| 61 | /** |
||||
| 62 | * Description: 得到跳转地址 |
||||
| 63 | * @author: JiaMeng <[email protected]> |
||||
| 64 | * Updater: |
||||
| 65 | * @return string |
||||
| 66 | */ |
||||
| 67 | public function getRedirectUrl() |
||||
| 68 | { |
||||
| 69 | //存储state |
||||
| 70 | $this->saveState(); |
||||
| 71 | //登录参数 |
||||
| 72 | $params = [ |
||||
| 73 | 'app_id' => $this->config['app_id'], |
||||
| 74 | 'redirect_uri' => $this->config['callback'], |
||||
| 75 | 'scope' => $this->config['scope'], |
||||
| 76 | 'state' => $this->config['state'], |
||||
| 77 | ]; |
||||
| 78 | |||||
| 79 | if ($this->config['is_sandbox'] == true) { |
||||
| 80 | //使用沙箱环境url |
||||
| 81 | $this->AuthorizeURL = str_replace("alipay", "alipaydev", $this->AuthorizeURL); |
||||
| 82 | } |
||||
| 83 | |||||
| 84 | return $this->AuthorizeURL . '?' . http_build_query($params); |
||||
| 85 | } |
||||
| 86 | |||||
| 87 | /** |
||||
| 88 | * Description: 获取当前授权用户的openid标识 |
||||
| 89 | * @author: JiaMeng <[email protected]> |
||||
| 90 | * Updater: |
||||
| 91 | * @return mixed |
||||
| 92 | * @throws OAuthException |
||||
| 93 | */ |
||||
| 94 | public function openid() |
||||
| 95 | { |
||||
| 96 | $this->getToken(); |
||||
| 97 | |||||
| 98 | if (isset($this->token['openid'])) { |
||||
| 99 | return $this->token['openid']; |
||||
| 100 | } else { |
||||
| 101 | throw new OAuthException('没有获取到支付宝用户ID!'); |
||||
| 102 | } |
||||
| 103 | } |
||||
| 104 | |||||
| 105 | /** |
||||
| 106 | * Description: 获取格式化后的用户信息 |
||||
| 107 | * @author: JiaMeng <[email protected]> |
||||
| 108 | * Updater: |
||||
| 109 | * @return array |
||||
| 110 | */ |
||||
| 111 | public function userInfo() |
||||
| 112 | { |
||||
| 113 | $result = $this->getUserInfo(); |
||||
| 114 | |||||
| 115 | $userInfo = [ |
||||
| 116 | 'open_id' => $this->openid(), |
||||
| 117 | 'union_id' => $this->token['union_id']??'', |
||||
| 118 | 'channel' => ConstCode::TYPE_ALIPAY, |
||||
| 119 | 'nickname' => $result['nick_name'], |
||||
| 120 | 'gender' => isset($result['gender']) ? $this->getGender($result['gender']) : ConstCode::GENDER, |
||||
| 121 | 'avatar' => $result['avatar'], |
||||
| 122 | // 拓展字段 |
||||
| 123 | 'access_token' => $this->token['access_token']??'', |
||||
| 124 | 'user_id' => $this->token['user_id'], |
||||
| 125 | 'native' => $result, |
||||
| 126 | ]; |
||||
| 127 | $userInfo['type'] = ConstCode::getTypeConst($userInfo['channel'],$this->type); |
||||
| 128 | return $userInfo; |
||||
| 129 | } |
||||
| 130 | |||||
| 131 | /** |
||||
| 132 | * Description: 获取原始接口返回的用户信息 |
||||
| 133 | * @author: JiaMeng <[email protected]> |
||||
| 134 | * Updater: |
||||
| 135 | * @return mixed |
||||
| 136 | * @throws OAuthException |
||||
| 137 | */ |
||||
| 138 | public function getUserInfo() |
||||
| 139 | { |
||||
| 140 | if($this->type == 'app'){//App登录 |
||||
| 141 | if(!isset($_REQUEST['access_token']) ){ |
||||
| 142 | throw new OAuthException("AliPay APP登录 需要传输access_token参数! "); |
||||
| 143 | } |
||||
| 144 | $this->token['access_token'] = $_REQUEST['access_token']; |
||||
| 145 | }else { |
||||
| 146 | /** 获取token信息 */ |
||||
| 147 | $this->getToken(); |
||||
| 148 | } |
||||
| 149 | |||||
| 150 | $params = [ |
||||
| 151 | 'app_id' => $this->config['app_id'], |
||||
| 152 | 'method' => 'alipay.user.info.share', |
||||
| 153 | 'charset' => 'UTF-8', |
||||
| 154 | 'sign_type' => $this->sign_type, |
||||
| 155 | 'timestamp' => date("Y-m-d H:i:s"), |
||||
| 156 | 'version' => '1.0', |
||||
| 157 | 'auth_token' => $this->token['access_token'], |
||||
| 158 | ]; |
||||
| 159 | $params['sign'] = $this->signature($params); |
||||
| 160 | |||||
| 161 | $data = $this->post(self::API_BASE, $params); |
||||
| 162 | $data = mb_convert_encoding($data, 'utf-8', 'gbk'); |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 163 | $result = json_decode($data, true); |
||||
|
0 ignored issues
–
show
It seems like
$data can also be of type array; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 164 | return $result['alipay_user_info_share_response']; |
||||
| 165 | } |
||||
| 166 | |||||
| 167 | /** |
||||
| 168 | * Description: 重写 获取的AccessToken请求参数 |
||||
| 169 | * @author: JiaMeng <[email protected]> |
||||
| 170 | * Updater: |
||||
| 171 | * @return array |
||||
| 172 | */ |
||||
| 173 | protected function accessTokenParams() |
||||
| 174 | { |
||||
| 175 | $params = [ |
||||
| 176 | 'app_id' => $this->config['app_id'], |
||||
| 177 | 'method' => 'alipay.system.oauth.token', |
||||
| 178 | 'charset' => 'UTF-8', |
||||
| 179 | 'sign_type' => $this->sign_type, |
||||
| 180 | 'timestamp' => date("Y-m-d H:i:s"), |
||||
| 181 | 'version' => '1.0', |
||||
| 182 | 'grant_type' => $this->config['grant_type'], |
||||
| 183 | 'code' => $this->getCode(), |
||||
| 184 | ]; |
||||
| 185 | $params['sign'] = $this->signature($params); |
||||
| 186 | return $params; |
||||
| 187 | } |
||||
| 188 | |||||
| 189 | /** |
||||
| 190 | * Description: 支付宝签名 |
||||
| 191 | * @author: JiaMeng <[email protected]> |
||||
| 192 | * Updater: |
||||
| 193 | * @param array $data |
||||
| 194 | * @return string |
||||
| 195 | * @throws OAuthException |
||||
| 196 | */ |
||||
| 197 | private function signature($data = []) |
||||
| 198 | { |
||||
| 199 | ksort($data); |
||||
| 200 | $str = Str::buildParams($data); |
||||
| 201 | |||||
| 202 | $rsaKey = $this->getRsaKeyVal(self::RSA_PRIVATE); |
||||
| 203 | $res = openssl_get_privatekey($rsaKey); |
||||
| 204 | if ($res !== false) { |
||||
| 205 | $sign = ''; |
||||
| 206 | openssl_sign($str, $sign, $res, OPENSSL_ALGO_SHA256); |
||||
| 207 | openssl_free_key($res); |
||||
| 208 | return base64_encode($sign); |
||||
| 209 | } |
||||
| 210 | throw new OAuthException('支付宝RSA私钥不正确'); |
||||
| 211 | } |
||||
| 212 | |||||
| 213 | /** |
||||
| 214 | * Description: 获取密钥 |
||||
| 215 | * @author: JiaMeng <[email protected]> |
||||
| 216 | * Updater: |
||||
| 217 | * @param int $type |
||||
| 218 | * @return string |
||||
| 219 | * @throws OAuthException |
||||
| 220 | */ |
||||
| 221 | private function getRsaKeyVal($type = self::RSA_PUBLIC) |
||||
| 222 | { |
||||
| 223 | if ($type === self::RSA_PUBLIC) { |
||||
| 224 | $keyname = 'pem_public'; |
||||
| 225 | $header = '-----BEGIN PUBLIC KEY-----'; |
||||
| 226 | $footer = '-----END PUBLIC KEY-----'; |
||||
| 227 | } else { |
||||
| 228 | $keyname = 'pem_private'; |
||||
| 229 | $header = '-----BEGIN RSA PRIVATE KEY-----'; |
||||
| 230 | $footer = '-----END RSA PRIVATE KEY-----'; |
||||
| 231 | } |
||||
| 232 | $rsa = $this->config[$keyname]; |
||||
| 233 | if (is_file($rsa)) { |
||||
| 234 | $rsa = file_get_contents($rsa); |
||||
| 235 | } |
||||
| 236 | if (empty($rsa)) { |
||||
| 237 | throw new OAuthException('支付宝RSA密钥未配置'); |
||||
| 238 | } |
||||
| 239 | $rsa = str_replace([PHP_EOL, $header, $footer], '', $rsa); |
||||
| 240 | $rsaVal = $header . PHP_EOL . chunk_split($rsa, 64, PHP_EOL) . $footer; |
||||
| 241 | return $rsaVal; |
||||
| 242 | } |
||||
| 243 | |||||
| 244 | /** |
||||
| 245 | * Description: 解析access_token方法 |
||||
| 246 | * @author: JiaMeng <[email protected]> |
||||
| 247 | * Updater: |
||||
| 248 | * @param $token |
||||
| 249 | * @return mixed |
||||
| 250 | * @throws OAuthException |
||||
| 251 | */ |
||||
| 252 | protected function parseToken($token) |
||||
| 253 | { |
||||
| 254 | $token = mb_convert_encoding($token, 'utf-8', 'gbk'); |
||||
| 255 | $data = json_decode($token, true); |
||||
|
0 ignored issues
–
show
It seems like
$token can also be of type array; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 256 | |||||
| 257 | if (isset($data['alipay_system_oauth_token_response'])) { |
||||
| 258 | $data = $data['alipay_system_oauth_token_response']; |
||||
| 259 | $data['openid'] = $data['open_id']??$data['user_id']; |
||||
| 260 | return $data; |
||||
| 261 | } else { |
||||
| 262 | throw new OAuthException("获取支付宝 ACCESS_TOKEN 出错:{$token}"); |
||||
| 263 | } |
||||
| 264 | } |
||||
| 265 | } |
||||
| 266 |