Apple::generateClientSecret()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 16
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 26
rs 9.7333
1
<?php
2
/**
3
 * Apple Sign In https://developer.apple.com/sign-in-with-apple/
4
 */
5
namespace tinymeng\OAuth2\Gateways;
6
7
use tinymeng\OAuth2\Connector\Gateway;
8
use tinymeng\OAuth2\Exception\OAuthException;
9
use tinymeng\OAuth2\Helper\ConstCode;
10
11
class Apple extends Gateway
12
{
13
    const API_BASE = 'https://appleid.apple.com/';
14
    protected $AuthorizeURL = 'https://appleid.apple.com/auth/authorize';
15
    protected $AccessTokenURL = 'https://appleid.apple.com/auth/token';
16
17
    /**
18
     * 得到跳转地址
19
     */
20
    public function getRedirectUrl()
21
    {
22
        $this->saveState();
23
        $params = [
24
            'client_id'     => $this->config['app_id'],
25
            'redirect_uri'  => $this->config['callback'],
26
            'response_type' => $this->config['response_type'],
27
            'scope'         => $this->config['scope'] ?: 'name email',
28
            'response_mode' => 'form_post',
29
            'state'         => $this->config['state'],
30
        ];
31
        return $this->AuthorizeURL . '?' . http_build_query($params);
32
    }
33
34
    /**
35
     * 获取当前授权用户的openid标识
36
     */
37
    public function openid()
38
    {
39
        $this->getToken();
40
        return $this->token['sub'] ?? '';
41
    }
42
43
    /**
44
     * 获取格式化后的用户信息
45
     */
46
    public function userInfo()
47
    {
48
        $userInfo = [
49
            'open_id'      => $this->openid(),
50
            'union_id'     => $this->token['sub'] ?? '',
51
            'channel'      => ConstCode::TYPE_APPLE,
52
            'nickname'     => $this->token['email'] ?? '',
53
            'gender'       => ConstCode::GENDER,
54
            'avatar'       => '',
55
            'access_token' => $this->token['access_token'] ?? '',
56
            'native'       => $this->token
57
        ];
58
        return $userInfo;
59
    }
60
61
    /**
62
     * 获取access_token
63
     */
64
    protected function getToken()
65
    {
66
        if (empty($this->token)) {
67
            $this->checkState();
68
            
69
            // 生成客户端密钥
70
            $clientSecret = $this->generateClientSecret();
71
            
72
            $params = [
73
                'client_id'     => $this->config['app_id'],
74
                'client_secret' => $clientSecret,
75
                'code'          => isset($_REQUEST['code']) ? $_REQUEST['code'] : '',
76
                'grant_type'    => $this->config['grant_type'],
77
                'redirect_uri'  => $this->config['callback'],
78
            ];
79
            
80
            $response = $this->post($this->AccessTokenURL, $params);
81
            $this->token = $this->parseToken($response);
82
            
83
            // 解析 ID Token
84
            $this->token = array_merge($this->token, $this->parseIdToken($this->token['id_token']));
85
        }
86
    }
87
88
    /**
89
     * 生成客户端密钥
90
     */
91
    protected function generateClientSecret()
92
    {
93
        $key = openssl_pkey_get_private($this->config['private_key']);
94
        if (!$key) {
95
            throw new OAuthException('私钥无效');
96
        }
97
98
        $headers = [
99
            'kid' => $this->config['key_id'],
100
            'alg' => 'ES256'
101
        ];
102
103
        $claims = [
104
            'iss' => $this->config['team_id'],
105
            'iat' => time(),
106
            'exp' => time() + 86400 * 180,
107
            'aud' => 'https://appleid.apple.com',
108
            'sub' => $this->config['app_id'],
109
        ];
110
111
        $payload = $this->base64UrlEncode(json_encode($headers)) . '.' . 
112
                  $this->base64UrlEncode(json_encode($claims));
113
114
        openssl_sign($payload, $signature, $key, OPENSSL_ALGO_SHA256);
115
        
116
        return $payload . '.' . $this->base64UrlEncode($signature);
117
    }
118
119
    /**
120
     * Base64Url 编码
121
     */
122
    protected function base64UrlEncode($data)
123
    {
124
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
125
    }
126
127
    /**
128
     * 解析 ID Token
129
     */
130
    protected function parseIdToken($idToken)
131
    {
132
        $tokens = explode('.', $idToken);
133
        if (count($tokens) != 3) {
134
            throw new OAuthException('无效的 ID Token');
135
        }
136
        
137
        $payload = json_decode(base64_decode($tokens[1]), true);
138
        if (!$payload) {
139
            throw new OAuthException('无效的 Token Payload');
140
        }
141
        
142
        return $payload;
143
    }
144
145
    /**
146
     * 解析access_token方法请求后的返回值
147
     */
148
    protected function parseToken($token)
149
    {
150
        $data = json_decode($token, true);
151
        if (isset($data['access_token'])) {
152
            return $data;
153
        }
154
        throw new OAuthException('获取Apple access_token 出错:' . json_encode($data));
155
    }
156
}