Completed
Push — master ( 609bd4...7ef3ac )
by LEUNG
07:43 queued 03:55
created

Jwt::getCacheIssuedAt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 1
cts 2
cp 0.5
crap 1.125
rs 10
1
<?php
2
3
namespace xiaodi;
4
5
use Lcobucci\JWT\Builder;
6
use Lcobucci\JWT\Parser;
7
use Lcobucci\JWT\Signer\Key;
8
use Lcobucci\JWT\Token;
9
use think\App;
10
use xiaodi\Exception\HasLoggedException;
11
use xiaodi\Exception\JWTException;
12
use xiaodi\Exception\JWTInvalidArgumentException;
13
use xiaodi\Exception\TokenAlreadyEexpired;
14
15
class Jwt
16
{
17
    private $token;
18
19
    private $blacklist;
20
21
    private $user;
22
23
    use \xiaodi\Traits\Jwt;
24
25
    public function __construct(App $app, Blacklist $blacklist)
26
    {
27
        $this->app = $app;
0 ignored issues
show
Bug Best Practice introduced by
The property app does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
28
        $this->blacklist = $blacklist;
29
        $this->builder = new Builder();
0 ignored issues
show
Bug Best Practice introduced by
The property builder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
30
31
        $config = $this->getConfig();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $config is correct as $this->getConfig() targeting xiaodi\Jwt::getConfig() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
32
        foreach ($config as $key => $v) {
0 ignored issues
show
Bug introduced by
The expression $config of type void is not traversable.
Loading history...
33
            $this->$key = $v;
34
        }
35
    }
36
37 15
    /**
38
     * 获取jwt配置.
39 15
     *
40 15
     * @return void
41
     */
42 15
    public function getConfig()
43 15
    {
44 15
        return $this->app->config->get('jwt');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->app->config->get('jwt') also could return the type array which is incompatible with the documented return type void.
Loading history...
45
    }
46 15
47
    /**
48 15
     * 生成 Token.
49
     *
50 15
     * @param array $claims
51
     *
52
     * @return \Lcobucci\JWT\Token
53
     */
54
    public function token(array $claims)
55
    {
56
        $time = time();
57
        $uniqid = uniqid();
58
59
        // 单点登录
60 12
        if ($this->sso()) {
61
            $sso_key = $this->ssoKey();
62 12
63 12
            if (empty($claims[$sso_key])) {
64
                throw new JWTInvalidArgumentException("未设置 \$claims['{$this->ssoKey}']值", 500);
65
            }
66 12
            $uniqid = $claims[$sso_key];
67 4
        }
68
69 3
        $this->builder->issuedAt($time)
70 1
            ->identifiedBy($uniqid, true)
71
            ->canOnlyBeUsedAfter($time + $this->notBefore())
72 2
            ->expiresAt($time + $this->ttl());
73
74
        foreach ($claims as $key => $claim) {
75 10
            $this->builder->withClaim($key, $claim);
76 10
        }
77 10
78 10
        $token = $this->builder->getToken($this->getSigner(), $this->makeKey());
79
80 10
        if (true === $this->sso()) {
81 10
            $this->setCacheIssuedAt($uniqid, $time);
82
        }
83
84 10
        return $token;
85
    }
86 9
87 2
    /**
88
     * 解析Token.
89
     *
90 9
     * @param string $token
91
     *
92
     * @return Token
93
     */
94
    public function parse(string $token)
95
    {
96
        try {
97
            $token = (new Parser())->parse($token);
98
        } catch (\InvalidArgumentException $e) {
99
            throw new JWTInvalidArgumentException('此 Token 解析失败', 500);
100 6
        }
101
102
        return $token;
103 6
    }
104 1
105 1
    protected function getRequestToken()
106
    {
107
        switch ($this->type) {
108 6
            case 'Header':
109
                $bearer = new BearerToken($this->app);
110
                $token = $bearer->getToken();
111
                break;
112
            case 'Cookie':
113
                $token = $this->app->cookie->get('token');
114
                break;
115
            case 'Url':
116
                $token = $this->app->request->param('token');
117
                break;
118
            default:
119
                $token = $this->app->request->param('token');
120
                break;
121
        }
122
123
        if (!$token) {
124
            throw new JwtException('获取Token失败.', 500);
125
        }
126
127
        return $token;
128
    }
129
130
    /**
131
     * 验证 Token.
132
     *
133
     * @param string $token
134
     *
135
     * @return bool
136
     */
137
    public function verify(string $token = '')
138
    {
139
        // 自动获取请求token
140
        if ($token == '') {
141
            $token = $this->getRequestToken();
142
        }
143
144
        // 解析Token
145
        $this->token = $this->parse($token);
146
147
        try {
148 5
            $this->validateToken();
149
            // 是否已过期
150
            if ($this->token->isExpired()) {
151 5
                if (time() < ($this->token->getClaim('iat') + $this->refreshExp())) {
0 ignored issues
show
Bug introduced by
The method refreshExp() does not exist on xiaodi\Jwt. Did you maybe mean refresh()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

151
                if (time() < ($this->token->getClaim('iat') + $this->/** @scrutinizer ignore-call */ refreshExp())) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
152
                    throw new TokenAlreadyEexpired('Token 已过期,请重新刷新', 401, $this->getAlreadyCode());
153
                } else {
154
                    throw new TokenAlreadyEexpired('Token 刷新时间已过,请重新登录', 401, $this->getReloginCode());
155
                }
156 5
            }
157
158
            // 单点登录
159 5
            if ($this->sso()) {
160
                $jwt_id = $this->token->getHeader('jti');
161 3
                // 当前Token签发时间
162 1
                $issued_at = $this->token->getClaim('iat');
163
                // 最新Token签发时间
164
                $cache_issued_at = $this->getCacheIssuedAt($jwt_id);
165
                if ($issued_at != $cache_issued_at) {
166 2
                    throw new HasLoggedException('此账号已在其它终端登录,请重新登录', 401, $this->getHasLoggedCode());
167 1
                }
168
            }
169 1
        } catch (\BadMethodCallException $e) {
170
            throw new JWTException('此 Token 未进行签名', 500);
171 1
        }
172 1
173 2
        return true;
174
    }
175
176 3
    /**
177
     * 效验 Token.
178
     *
179
     * @return void
180 2
     */
181
    protected function validateToken()
182
    {
183 5
        // 验证密钥是否与创建签名的密钥匹配
184
        if (false === $this->token->verify($this->getSigner(), $this->makeKey())) {
185
            throw new JWTException('此 Token 与 密钥不匹配', 500);
186 5
        }
187 1
188
        // 是否可用
189
        $exp = $this->token->getClaim('nbf');
190
        if (time() < $exp) {
191 4
            throw new JWTException('此 Token 暂未可用', 500);
192 4
        }
193 1
194
        if ($this->blacklist->has($this->token)) {
195 3
            throw new JWTException('此 Token 已注销', 500);
196
        }
197
    }
198
199
    /**
200
     * 获取 Token 对象.
201
     *
202
     * @return \Lcobucci\JWT\Token
203
     */
204
    public function getToken()
205 2
    {
206
        return $this->token;
207 2
    }
208 2
209
    /**
210 2
     * 刷新 Token.
211 2
     *
212
     * @param Token $token
213
     *
214
     * @return Token
215
     */
216
    public function refresh(Token $token)
217
    {
218
        // 移除Token
219
        $this->remove($token);
220 1
221
        $claims = $token->getClaims();
222 1
223
        unset($claims['iat']);
224
        unset($claims['jti']);
225
        unset($claims['nbf']);
226
        unset($claims['exp']);
227
        unset($claims['iss']);
228
        unset($claims['aud']);
229
230
        return $this->token($claims);
231
    }
232
233
    /**
234
     * 删除 Token.
235
     *
236
     * @param Token $token
237
     *
238
     * @return void
239
     */
240 1
    public function remove(Token $token)
241
    {
242 1
        $this->blacklist->push($token);
243
    }
244 1
245 1
    /**
246 1
     * 生成私钥.
247 1
     *
248 1
     * @return Key
249 1
     */
250
    private function makeKey()
251 1
    {
252
        $key = $this->getSignerKey();
253
        if (empty($key)) {
254
            throw new JWTException('私钥未配置.', 500);
255
        }
256
257
        return new Key($key);
258
    }
259 9
260
    /**
261 9
     * 缓存最新签发时间.
262 9
     *
263
     * @param string|int $jwt_id 唯一标识
264
     * @param string     $value  签发时间
265
     *
266 9
     * @return void
267
     */
268
    public function setCacheIssuedAt($jwt_id, $value)
269
    {
270
        $key = $this->ssoCachePrefix . '-' . $jwt_id;
0 ignored issues
show
Bug introduced by
The property ssoCachePrefix does not exist on xiaodi\Jwt. Did you mean ssoCacheKey?
Loading history...
271
        $ttl = $this->ttl() + $this->notBefore();
272
273
        $this->app->cache->set($key, $value, $ttl);
274 10
    }
275
276 10
    /**
277
     * 获取最新签发时间.
278 10
     *
279 1
     * @param string|int $jwt_id 唯一标识
280
     *
281
     * @return string
282 9
     */
283
    protected function getCacheIssuedAt($jwt_id)
284 9
    {
285
        return $this->app->cache->get($this->ssoCachePrefix . '-' . $jwt_id);
0 ignored issues
show
Bug introduced by
The property ssoCachePrefix does not exist on xiaodi\Jwt. Did you mean ssoCacheKey?
Loading history...
286
    }
287
288 9
    /**
289
     * 是否注入用户对象.
290
     *
291
     * @return bool
292
     */
293
    public function injectUser()
294
    {
295
        return $this->injectUser;
296 1
    }
297
298 1
    /**
299 1
     * 获取用户模型.
300
     *
301
     * @return void
302
     */
303
    public function userModel()
304
    {
305
        return $this->userModel;
306
    }
307
308
    /**
309
     * 获取用户模型对象
310
     *
311
     * @return void
312
     */
313
    public function user()
314
    {
315
        $uid = $this->token->getClaim($this->ssoKey());
316
        if ($uid) {
317
            $namespace = $this->userModel();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $namespace is correct as $this->userModel() targeting xiaodi\Jwt::userModel() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
318
            if (empty($namespace)) {
319
                throw new JWTInvalidArgumentException('用户模型文件未配置.', 500);
320
            }
321
322
            $r = new \ReflectionClass($namespace);
323
            $model = $r->newInstance();
324
            $this->user = $model->find($uid);
325
        }
326
327
        return $this->user;
328
    }
329
}
330