我们的lumen项目中使用到了jwt,网上资料也有很多对jwt的分析,以及jwt拓展使用的
这里记录一下jwt的token生成,以及流程,了解token的来源。
项目中的login接口中使用登录者信息调用attempt
接口获取$token 追根溯源从这里开始
$credentials = ['username' =>'123', 'password' => '123456';
$token = Auth::guard('user')->attempt($credentials);
Auth::guard('user')
返回了一个看守器实例。jwt拓展详解
public static function attempt($credentials = array(), $login = true)
{
return \Tymon\JWTAuth\JWTGuard::attempt($credentials, $login);
}
代码追踪找到attempt实现的是JWTGuard类下的attempt接口:对用户身份进行验证并返回令牌
验证通过调用login
方法登录并返回令牌
public function attempt(array $credentials = [], $login = true)
{
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
if ($this->hasValidCredentials($user, $credentials)) {
return $login ? $this->login($user) : true;
}
return false;
}
$this->provider
就是 auth.php
配置文件的providers
配置项
/*
|--------------------------------------------------------------------------
| 提供器配置
|--------------------------------------------------------------------------
|
| Supported: "database", "eloquent"
| 提供器定义怎么检索用户
|
*/
'providers' => [
'admins' => [
'driver' => 'eloquent',
//'table'=>'t_platform_admin',
'model' => App\Http\Models\Auth\Admin::class,
],
'users' => [
'driver' => 'eloquent',
//'table'=>'t_admin',
'model' => App\Http\Models\Auth\User::class,
],
],
eloquent
就是 EloquentUserProvider
类,在这里我们找到了attempt
调用的retrieveByCredentials
方法.根据最开始给attempt
方法的登录用户信息,去数据库中查找匹配
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
$query->where($key, $value);
}
}
return $query->first();
}
接下来就是用户信息的校验了,具体实现同样在 EloquentUserProvider
类
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
$this->hasher->check
在 Illuminate\Hashing\BcryptHasher
/**
* Check the given plain value against a hash.
*
* @param string $value
* @param string $hashedValue
* @param array $options
* @return bool
*/
public function check($value, $hashedValue, array $options = [])
{
if (strlen($hashedValue) === 0) {
return false;
}
return password_verify($value, $hashedValue);
}
至此完成用户信息校验, attempt
进入 $this->login($user)
方法.
/**
* Create a token for a user.
*
* @param \Tymon\JWTAuth\Contracts\JWTSubject $user
*
* @return string
*/
public function login(JWTSubject $user)
{
$token = $this->jwt->fromUser($user);
$this->setToken($token)->setUser($user);
return $token;
}
$this->jwt->fromUser($user);
在Tymon\JWTAuth\JWT
中
public function fromUser(JWTSubject $user)
{
return $this->fromSubject($user);
}
public function fromSubject(JWTSubject $subject)
{
$payload = $this->makePayload($subject);//生成载荷
return $this->manager->encode($payload)->get();
}
public function makePayload(JWTSubject $subject)
{
//生成载荷进入factory类
return $this->factory()->customClaims($this->getClaimsArray($subject))->make();
}
Tymon\JWTAuth\Factory
public function customClaims(array $customClaims)
{
$this->customClaims = $customClaims;
return $this;
}
public function make($resetClaims = false)
{
$payload = $this->withClaims($this->buildClaimsCollection());
if ($resetClaims) {
$this->emptyClaims();
}
return $payload;
}
public function buildClaimsCollection()
{
return $this->buildClaims()->resolveClaims();
}
protected function buildClaims()
{
// remove the exp claim if it exists and the ttl is null
if ($this->claimFactory->getTTL() === null && $key = array_search('exp', $this->defaultClaims)) {
unset($this->defaultClaims[$key]);
}
// add the default claims
foreach ($this->defaultClaims as $claim) {
$this->addClaim($claim, $this->claimFactory->make($claim));
}
// add custom claims on top, allowing them to overwrite defaults
return $this->addClaims($this->getCustomClaims());
}
protected function resolveClaims()
{
return $this->claims->map(function ($value, $name) {
return $value instanceof Claim ? $value : $this->claimFactory->get($name, $value);
});
}
public function map(callable $callback)
{
$keys = array_keys($this->items);
$items = array_map($callback, $this->items, $keys);
return new static(array_combine($keys, $items));
}
make()
函数返回 $payload
token中载荷
payload
之后,是signer的生成, 实例调用Tymon\JWTAuth\Providers\JWT\Namshi
类的encode
方法
$this->manager->encode($payload)->get();
public function encode(array $payload)
{
try {
$this->jws->setPayload($payload)->sign($this->getSigningKey(), $this->getPassphrase());
return (string) $this->jws->getTokenString();
} catch (Exception $e) {
throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e);
}
}
encode
方法实现 Namshi\JOSE\JWS
类
public function sign($key, $password = null)
{
$this->signature = $this->getSigner()->sign($this->generateSigninInput(), $key, $password);
$this->isSigned = true;
return $this->signature;
}
getSigner()
是通过获取到加密类型,来实例化Namshi\JOSE\Signer
下指定的类.项目中使用的就是Namshi\JOSE\Signer\OpenSSL\HS256
类.这个类继承了HMAC
父类,而HMAC
就是具体signer的实现类
public function sign($input, $key)
{
return hash_hmac($this->getHashingAlgorithm(), $input, (string) $key, true);
}
这里的($this->generateSigninInput()
就是payload
和header
的内容 $key
是JWT_SECRET
的密匙 ,$password是非对称加密下签署令牌的密码.用于生成token的第三部分signature
$this->jws->getTokenString();
public function getTokenString()
{
$signinInput = $this->generateSigninInput();
return sprintf('%s.%s', $signinInput, $this->encoder->encode($this->getSignature()));
}
public function generateSigninInput()
{
$base64payload = $this->encoder->encode(json_encode($this->getPayload(), JSON_UNESCAPED_SLASHES));
$base64header = $this->encoder->encode(json_encode($this->getHeader(), JSON_UNESCAPED_SLASHES));
return sprintf('%s.%s', $base64header, $base64payload);
}
先拼接了header
和payload
再拼接了Signature
返回token
Tymon\JWTAuth\Providers\JWT\Lcobucci
类也实现了token
的生成,不同的是通过公钥和私钥来生成 token
,这里流程不再赘述,代码如下:
public function encode(array $payload)
{
// Remove the signature on the builder instance first.
$this->builder->unsign();
try {
foreach ($payload as $key => $value) {
$this->builder->set($key, $value);
}
$this->builder->sign($this->signer, $this->getSigningKey());
} catch (Exception $e) {
throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e);
}
return (string) $this->builder->getToken();
}