内容简介:Laravel Passport——OAuth2 API 认证系统源码解析(下)
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
隐式授权
隐式授权类似于授权码授权,但是它只令牌将返回给客户端而不交换授权码。这种授权最常用于无法安全存储客户端凭据的 JavaScript 或移动应用程序。通过调用 AuthServiceProvider
中的 enableImplicitGrant
方法来启用这种授权:
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::enableImplicitGrant();
}
调用上面方法开启授权后,开发者可以使用他们的客户端 ID 从应用程序请求访问令牌。接入的应用程序应该向你的应用程序的 /oauth/authorize 路由发出重定向请求,如下所示:
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://example.com/callback',
'response_type' => 'token',
'scope' => '',
]);
return redirect('http://your-app.com/oauth/authorize?'.$query);
});
首先仍然是验证授权请求的合法性,其流程与授权码模式基本一致:
public function validateAuthorizationRequest(ServerRequestInterface $request)
{
$clientId = $this->getQueryStringParameter(
'client_id',
$request,
$this->getServerParameter('PHP_AUTH_USER', $request)
);
if (is_null($clientId)) {
throw OAuthServerException::invalidRequest('client_id');
}
$client = $this->clientRepository->getClientEntity(
$clientId,
$this->getIdentifier(),
null,
false
);
if ($client instanceof ClientEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
$scopes = $this->validateScopes(
$this->getQueryStringParameter('scope', $request, $this->defaultScope),
is_array($client->getRedirectUri())
? $client->getRedirectUri()[0]
: $client->getRedirectUri()
);
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$scopes,
$this->getIdentifier(),
$client
);
$stateParameter = $this->getQueryStringParameter('state', $request);
$authorizationRequest = new AuthorizationRequest();
$authorizationRequest->setGrantTypeId($this->getIdentifier());
$authorizationRequest->setClient($client);
$authorizationRequest->setRedirectUri($redirectUri);
$authorizationRequest->setState($stateParameter);
$authorizationRequest->setScopes($finalizedScopes);
return $authorizationRequest;
}
接着,当用户同意授权之后,就要直接返回 access_token
,League OAuth2
直接将令牌放入 JWT
中发送回第三方客户端,值得注意的是依据 OAuth2
标准,参数都是以 location hash
的形式返回的,间隔符是 #
,而不是 ?
:
public function __construct(\DateInterval $accessTokenTTL, $queryDelimiter = '#')
{
$this->accessTokenTTL = $accessTokenTTL;
$this->queryDelimiter = $queryDelimiter;
}
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
}
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
? is_array($authorizationRequest->getClient()->getRedirectUri())
? $authorizationRequest->getClient()->getRedirectUri()[0]
: $authorizationRequest->getClient()->getRedirectUri()
: $authorizationRequest->getRedirectUri();
// The user approved the client, redirect them back with an access token
if ($authorizationRequest->isAuthorizationApproved() === true) {
$accessToken = $this->issueAccessToken(
$this->accessTokenTTL,
$authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier(),
$authorizationRequest->getScopes()
);
$response = new RedirectResponse();
$response->setRedirectUri(
$this->makeRedirectUri(
$finalRedirectUri,
[
'access_token' => (string) $accessToken->convertToJWT($this->privateKey),
'token_type' => 'Bearer',
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(),
'state' => $authorizationRequest->getState(),
],
$this->queryDelimiter
)
);
return $response;
}
// The user denied the client, redirect them back with an error
throw OAuthServerException::accessDenied(
'The user denied the request',
$this->makeRedirectUri(
$finalRedirectUri,
[
'state' => $authorizationRequest->getState(),
]
)
);
}
这个用于构建 jwt
的私钥就是 oauth-private.key
,我们知道,jwt
一般有三个部分组成:header
、claim
、sign
, 用于 oauth2
的 jwt
中 claim
主要构成有:
- aud 客户端 id
- jti access_token 随机码
- iat 生成时间
- nbf 拒绝接受 jwt 时间
- exp access_token 失效时间
- sub 用户 id
具体可以参考 : JSON Web Token (JWT) draft-ietf-oauth-json-web-token-32
public function convertToJWT(CryptKey $privateKey)
{
return (new Builder())
->setAudience($this->getClient()->getIdentifier())
->setId($this->getIdentifier(), true)
->setIssuedAt(time())
->setNotBefore(time())
->setExpiration($this->getExpiryDateTime()->getTimestamp())
->setSubject($this->getUserIdentifier())
->set('scopes', $this->getScopes())
->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase()))
->getToken();
}
public function __construct(
Encoder $encoder = null,
ClaimFactory $claimFactory = null
) {
$this->encoder = $encoder ?: new Encoder();
$this->claimFactory = $claimFactory ?: new ClaimFactory();
$this->headers = ['typ'=> 'JWT', 'alg' => 'none'];
$this->claims = [];
}
public function setAudience($audience, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('aud', (string) $audience, $replicateAsHeader);
}
public function setId($id, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('jti', (string) $id, $replicateAsHeader);
}
public function setIssuedAt($issuedAt, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('iat', (int) $issuedAt, $replicateAsHeader);
}
public function setNotBefore($notBefore, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('nbf', (int) $notBefore, $replicateAsHeader);
}
public function setExpiration($expiration, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('exp', (int) $expiration, $replicateAsHeader);
}
public function setSubject($subject, $replicateAsHeader = false)
{
return $this->setRegisteredClaim('sub', (string) $subject, $replicateAsHeader);
}
public function sign(Signer $signer, $key)
{
$signer->modifyHeader($this->headers);
$this->signature = $signer->sign(
$this->getToken()->getPayload(),
$key
);
return $this;
}
public function getToken()
{
$payload = [
$this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->headers)),
$this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->claims))
];
if ($this->signature !== null) {
$payload[] = $this->encoder->base64UrlEncode($this->signature);
}
return new Token($this->headers, $this->claims, $this->signature, $payload);
}
根据 JWT 的生成方法,签名部分 signature
是 header
与 claim
进行 base64
编码后再加密的结果。
客户端模式
客户端凭据授权适用于机器到机器的认证。例如,你可以在通过 API 执行维护任务中使用此授权。要使用这种授权,你首先需要在 app/Http/Kernel.php 的 routeMiddleware 变量中添加新的中间件:
protected $routeMiddleware = [
'client' => CheckClientCredentials::class,
];
Route::get('/user', function(Request $request) {
...
})->middleware('client');
接下来通过向 oauth/token 接口发出请求来获取令牌:
$response = $guzzle->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => 'your-scope',
],
]);
echo json_decode((string) $response->getBody(), true);
客户端模式类似于授权码模式的后一部分,利用客户端 id 与客户端密码来获取 access_token
:
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client);
// Issue and persist access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes);
// Inject access token into response type
$responseType->setAccessToken($accessToken);
return $responseType;
}
类似于授权码模式,access_token
的发放也是通过 Bearer Token
中存放 JWT。
密码模式
OAuth2 密码授权机制可以让你自己的客户端(如移动应用程序)邮箱地址或者用户名和密码获取访问令牌。如此一来你就可以安全地向自己的客户端发出访问令牌,而不需要遍历整个 OAuth2 授权代码重定向流程。
创建密码授权的客户端后,就可以通过向用户的电子邮件地址和密码向 /oauth/token 路由发出 POST 请求来获取访问令牌。而该路由已经由 Passport::routes 方法注册,因此不需要手动定义它。如果请求成功,会在服务端返回的 JSON 响应中收到一个 access_token 和 refresh_token:
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
只要用用户名与密码来验证合法性就可以发放 access_token
与 refresh_token
:
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
$user = $this->validateUser($request, $client);
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
// Issue and persist new tokens
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
$refreshToken = $this->issueRefreshToken($accessToken);
// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
return $responseType;
}
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
{
$username = $this->getRequestParameter('username', $request);
if (is_null($username)) {
throw OAuthServerException::invalidRequest('username');
}
$password = $this->getRequestParameter('password', $request);
if (is_null($password)) {
throw OAuthServerException::invalidRequest('password');
}
$user = $this->userRepository->getUserEntityByUserCredentials(
$username,
$password,
$this->getIdentifier(),
$client
);
if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
}
return $user;
}
路由保护
Passport 包含一个 验证保护机制 可以验证请求中传入的访问令牌。配置 api 的看守器使用 passport 驱动程序后,只需要在需要有效访问令牌的任何路由上指定 auth:api 中间件:
Route::get('/user', function () {
//
})->middleware('auth:api');
当调用 Passport 保护下的路由时,接入的 API 应用需要将访问令牌作为 Bearer 令牌放在请求头 Authorization 中。例如,使用 Guzzle HTTP 库时:
$response = $client->request('GET', '/api/user', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$accessToken,
],
]);
auth:api 中间件
当我们已经配置完成 Passport
的四种模式并拿到 access_token
之后,我们就可以利用令牌去资源服务器获取数据了。资源服务器最常用的校验令牌的中间件就是 auth:api
,中间件是 auth
,api
是中间件的参数:
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
这个中间件是验证登录状态的常用中间件:
class Authenticate
{
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
}
我们的参数 api
就是上面的 guards
,Auth
是 laravel
自带的登录校验服务:
class AuthManager implements FactoryContract
{
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
}
}
文档告诉我们,若想要使用 passport
服务,我们的 config/auth
文件需要如此配置:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
可以看出,driver
就是 passport
,我们在启动 passport
服务的时候曾经注册过一个 Guard
:
protected function registerGuard()
{
Auth::extend('passport', function ($app, $name, array $config) {
return tap($this->makeGuard($config), function ($guard) {
$this->app->refresh('request', $guard, 'setRequest');
});
});
}
protected function makeGuard(array $config)
{
return new RequestGuard(function ($request) use ($config) {
return (new TokenGuard(
$this->app->make(ResourceServer::class),
Auth::createUserProvider($config['provider']),
$this->app->make(TokenRepository::class),
$this->app->make(ClientRepository::class),
$this->app->make('encrypter')
))->user($request);
}, $this->app['request']);
}
因此,passport
使用的就是这个 TokenGuard
:
class TokenGuard
{
public function __construct(ResourceServer $server,
UserProvider $provider,
TokenRepository $tokens,
ClientRepository $clients,
Encrypter $encrypter)
{
$this->server = $server;
$this->tokens = $tokens;
$this->clients = $clients;
$this->provider = $provider;
$this->encrypter = $encrypter;
}
public function user(Request $request)
{
if ($request->bearerToken()) {
return $this->authenticateViaBearerToken($request);
} elseif ($request->cookie(Passport::cookie())) {
return $this->authenticateViaCookie($request);
}
}
}
可以看到,TokenGuard
支持两种 Token
的验证:BearerToken
与 cookie
。
我们首先看 BearerToken
:
public function bearerToken()
{
$header = $this->header('Authorization', '');
if (Str::startsWith($header, 'Bearer ')) {
return Str::substr($header, 7);
}
}
protected function authenticateViaBearerToken($request)
{
$psr = (new DiactorosFactory)->createRequest($request);
try {
$psr = $this->server->validateAuthenticatedRequest($psr);
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);
if (! $user) {
return;
}
$token = $this->tokens->find(
$psr->getAttribute('oauth_access_token_id')
);
$clientId = $psr->getAttribute('oauth_client_id');
if ($this->clients->revoked($clientId)) {
return;
}
return $token ? $user->withAccessToken($token) : null;
} catch (OAuthServerException $e) {
return Container::getInstance()->make(
ExceptionHandler::class
)->report($e);
}
}
首先,需要验证请求的合法性:
class ResourceServer
{
public function validateAuthenticatedRequest(ServerRequestInterface $request)
{
return $this->getAuthorizationValidator()->validateAuthorization($request);
}
protected function getAuthorizationValidator()
{
if ($this->authorizationValidator instanceof AuthorizationValidatorInterface === false) {
$this->authorizationValidator = new BearerTokenValidator($this->accessTokenRepository);
}
$this->authorizationValidator->setPublicKey($this->publicKey);
return $this->authorizationValidator;
}
}
BearerTokenValidator
专门用于验证 BearerToken
的合法性:
class BearerTokenValidator implements AuthorizationValidatorInterface
{
public function validateAuthorization(ServerRequestInterface $request)
{
if ($request->hasHeader('authorization') === false) {
throw OAuthServerException::accessDenied('Missing "Authorization" header');
}
$header = $request->getHeader('authorization');
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0]));
try {
// Attempt to parse and validate the JWT
$token = (new Parser())->parse($jwt);
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
throw OAuthServerException::accessDenied('Access token could not be verified');
}
// Ensure access token hasn't expired
$data = new ValidationData();
$data->setCurrentTime(time());
if ($token->validate($data) === false) {
throw OAuthServerException::accessDenied('Access token is invalid');
}
// Check if token has been revoked
if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) {
throw OAuthServerException::accessDenied('Access token has been revoked');
}
// Return the request with additional attributes
return $request
->withAttribute('oauth_access_token_id', $token->getClaim('jti'))
->withAttribute('oauth_client_id', $token->getClaim('aud'))
->withAttribute('oauth_user_id', $token->getClaim('sub'))
->withAttribute('oauth_scopes', $token->getClaim('scopes'));
} catch (\InvalidArgumentException $exception) {
// JWT couldn't be parsed so return the request as is
throw OAuthServerException::accessDenied($exception->getMessage());
} catch (\RuntimeException $exception) {
//JWR couldn't be parsed so return the request as is
throw OAuthServerException::accessDenied('Error while decoding to JSON');
}
}
}
通过 passport
拿到的 access_token
都是 JWT
格式的,因此首先第一步需要将 JWT
解析:
class Parser
{
public function parse($jwt)
{
$data = $this->splitJwt($jwt);
$header = $this->parseHeader($data[0]);
$claims = $this->parseClaims($data[1]);
$signature = $this->parseSignature($header, $data[2]);
foreach ($claims as $name => $value) {
if (isset($header[$name])) {
$header[$name] = $value;
}
}
if ($signature === null) {
unset($data[2]);
}
return new Token($header, $claims, $signature, $data);
}
protected function splitJwt($jwt)
{
if (!is_string($jwt)) {
throw new InvalidArgumentException('The JWT string must have two dots');
}
$data = explode('.', $jwt);
if (count($data) != 3) {
throw new InvalidArgumentException('The JWT string must have two dots');
}
return $data;
}
protected function parseHeader($data)
{
$header = (array) $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
if (isset($header['enc'])) {
throw new InvalidArgumentException('Encryption is not supported yet');
}
return $header;
}
protected function parseClaims($data)
{
$claims = (array) $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));
foreach ($claims as $name => &$value) {
$value = $this->claimFactory->create($name, $value);
}
return $claims;
}
protected function parseSignature(array $header, $data)
{
if ($data == '' || !isset($header['alg']) || $header['alg'] == 'none') {
return null;
}
$hash = $this->decoder->base64UrlDecode($data);
return new Signature($hash);
}
}
获得 JWT
的三个部分之后,就要验证签名部分是否合法:
class Token
{
public function verify(Signer $signer, $key)
{
if ($this->signature === null) {
throw new BadMethodCallException('This token is not signed');
}
if ($this->headers['alg'] !== $signer->getAlgorithmId()) {
return false;
}
return $this->signature->verify($signer, $this->getPayload(), $key);
}
}
验证通过之后,就要验证 JWT
各个部分是否合法:
$data = new ValidationData();
$data->setCurrentTime(time());
public function __construct($currentTime = null)
{
$currentTime = $currentTime ?: time();
$this->items = [
'jti' => null,
'iss' => null,
'aud' => null,
'sub' => null,
'iat' => $currentTime,
'nbf' => $currentTime,
'exp' => $currentTime
];
}
public function validate(ValidationData $data)
{
foreach ($this->getValidatableClaims() as $claim) {
if (!$claim->validate($data)) {
return false;
}
}
return true;
}
public function __construct(array $callbacks = [])
{
$this->callbacks = array_merge(
[
'iat' => [$this, 'createLesserOrEqualsTo'],
'nbf' => [$this, 'createLesserOrEqualsTo'],
'exp' => [$this, 'createGreaterOrEqualsTo'],
'iss' => [$this, 'createEqualsTo'],
'aud' => [$this, 'createEqualsTo'],
'sub' => [$this, 'createEqualsTo'],
'jti' => [$this, 'createEqualsTo']
],
$callbacks
);
}
我们前面说过,
- aud 客户端 id
- jti access_token 随机码
- iat 生成时间
- nbf 拒绝接受 jwt 时间
- exp access_token 失效时间
- sub 用户 id
因此,JWT
的生成时间、拒绝接受时间、失效时间就会被验证完成。
接下来,还会验证最重要的 access_token
:
if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) {
throw OAuthServerException::accessDenied('Access token has been revoked');
}
public function isAccessTokenRevoked($tokenId)
{
return $this->tokenRepository->isAccessTokenRevoked($tokenId);
}
public function isAccessTokenRevoked($id)
{
if ($token = $this->find($id)) {
return $token->revoked;
}
return true;
}
接下来,TokenGuard
就会验证 userid
、clientid
与 access_token
的合法性:
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);
if (! $user) {
return;
}
$token = $this->tokens->find(
$psr->getAttribute('oauth_access_token_id')
);
$clientId = $psr->getAttribute('oauth_client_id');
if ($this->clients->revoked($clientId)) {
return;
}
return $token ? $user->withAccessToken($token) : null;
中间件验证完成。
客户端模式中间件 CheckClientCredentials
我们在上面可以看到 auth:api
中间件不仅验证 access_token
,还会验证 user_id
,对于客户端模式来说,由于 JWT
中并没有用户信息,因此 passport
专门存在中间件 CheckClientCredentials
来做非登录状态的校验。
class CheckClientCredentials
{
public function handle($request, Closure $next, ...$scopes)
{
$psr = (new DiactorosFactory)->createRequest($request);
try {
$psr = $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
$this->validateScopes($psr, $scopes);
return $next($request);
}
}
使用 JavaScript 接入 API
在构建 API 时,如果能通过 JavaScript 应用接入自己的 API 将会给开发过程带来极大的便利。这种 API 开发方法允许你使用自己的应用程序的 API 和别人共享的 API。你的 Web 应用程序、移动应用程序、第三方应用程序以及可能在各种软件包管理器上发布的任何 SDK 都可能会使用相同的API。
通常,如果要从 JavaScript 应用程序中使用 API,则需要手动向应用程序发送访问令牌,并将其传递给应用程序。但是,Passport 有一个可以处理这个问题的中间件。将 CreateFreshApiToken 中间件添加到 web 中间件组就可以了:
'web' => [
// Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
Passport 的这个中间件将会在你所有的对外请求中添加一个 laravel_token cookie。该 cookie 将包含一个加密后的 JWT ,Passport 将用来验证来自 JavaScript 应用程序的 API 请求。至此,你可以在不明确传递访问令牌的情况下向应用程序的 API 发出请求
axios.get('/user')
.then(response => {
console.log(response.data);
});
当使用上面的授权方法时,Axios 会自动带上 X-CSRF-TOKEN 请求头传递。另外,默认的 Laravel JavaScript 脚手架会让 Axios 发送 X-Requested-With 请求头:
window.axios.defaults.headers.common = {
'X-Requested-With': 'XMLHttpRequest',
};
CreateFreshApiToken 中间件
class CreateFreshApiToken
{
public function handle($request, Closure $next, $guard = null)
{
$this->guard = $guard;
$response = $next($request);
if ($this->shouldReceiveFreshToken($request, $response)) {
$response->withCookie($this->cookieFactory->make(
$request->user($this->guard)->getKey(), $request->session()->token()
));
}
return $response;
}
public function make($userId, $csrfToken)
{
$config = $this->config->get('session');
$expiration = Carbon::now()->addMinutes($config['lifetime']);
return new Cookie(
Passport::cookie(),
$this->createToken($userId, $csrfToken, $expiration),
$expiration,
$config['path'],
$config['domain'],
$config['secure'],
true
);
}
protected function createToken($userId, $csrfToken, Carbon $expiration)
{
return JWT::encode([
'sub' => $userId,
'csrf' => $csrfToken,
'expiry' => $expiration->getTimestamp(),
], $this->encrypter->getKey());
}
protected function shouldReceiveFreshToken($request, $response)
{
return $this->requestShouldReceiveFreshToken($request) &&
$this->responseShouldReceiveFreshToken($response);
}
protected function requestShouldReceiveFreshToken($request)
{
return $request->isMethod('GET') && $request->user($this->guard);
}
protected function responseShouldReceiveFreshToken($response)
{
return $response instanceof Response && ! $this->alreadyContainsToken($response);
}
}
这个中间件发出的 JWT
令牌仍然由 auth:api
来负责验证,我们前面说过,TokenGuard
负责两种令牌的验证,一种是 BearerToken
, 另一种就是这个 Cookie
:
public function user(Request $request)
{
if ($request->bearerToken()) {
return $this->authenticateViaBearerToken($request);
} elseif ($request->cookie(Passport::cookie())) {
return $this->authenticateViaCookie($request);
}
}
protected function authenticateViaCookie($request)
{
try {
$token = $this->decodeJwtTokenCookie($request);
} catch (Exception $e) {
return;
}
if (! $this->validCsrf($token, $request) ||
time() >= $token['expiry']) {
return;
}
if ($user = $this->provider->retrieveById($token['sub'])) {
return $user->withAccessToken(new TransientToken);
}
}
protected function decodeJwtTokenCookie($request)
{
return (array) JWT::decode(
$this->encrypter->decrypt($request->cookie(Passport::cookie())),
$this->encrypter->getKey(), ['HS256']
);
}
protected function validCsrf($token, $request)
{
return isset($token['csrf']) && hash_equals(
$token['csrf'], (string) $request->header('X-CSRF-TOKEN')
);
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
1024·人与机器共同进化
东西文库 / 译言·东西文库/电子工业出版社 / 2013-12-20 / 55元
《1024》:国内第一本专注于科技文化的mook。 本期创刊号将目光定焦在“人与机器”这个超热点领域。 如果把机器获得思维能力看作是一种进化, 那人类具备不朽之躯同样也是一种进化。 这是一个野心勃勃但又充满不确定性的未来。 在我们一厢情愿地猜测机器将在不远的将来赶超自己而惶惶不可终日时,人类其实还有一个机会——变得更像机器。这并非科幻小说,而是正在发生的现实。人类创造......一起来看看 《1024·人与机器共同进化》 这本书的介绍吧!