laravel Oauth Password集成三方登录,不用密码生成password模式的token

需求 用户忘记密码,需要使用短信验证码给他下发新的token。同样适用于三方登录,短信验证码登录。

条件

本来我是想直接上redis,用手机号做key,绑定一个随机sign签名来做用户鉴权。但这样来做的话,好像有点不太合适。因为项目本身就是前后端分离,鉴权这事儿肯定还是走token更合适。
不过我们数据库存的都是Hash过的密码,用户没有明文密码,就无法正常的走password方式获取token。

尝试1

网上有直接修改Model里校验身份信息的方法,看起来是可以。

  $query->where('username'=12)->where('password'=明文)->orwhere('password'=加密过的密码);

但是这样有很大的风险,不建议这么做,如果数据库泄露,那么别人直接就能拿密文密码登录。

尝试2

laravel社区里说文档里有方法

User->getAccessToken()

尝试过后发现这是生成person模式的token,我这里只保留了password模式,所以也不行。

尝试3

曾经5.7的时候写过一篇文章:
https://blog.dugwang.com/?p=887
两步完成Laravel 5.7 Auth组件重写密码认证方式为MD5加密,并使用Oauth+password生成新的Bear Token进行前后端通讯
里面是使用了trait重写生成password的方式,套用了后发现不兼容现在的laravel5.8^版本。

修改后的代码如下

<?php

namespace App\Traits;
//这里请引用自己的User Model
use App\Http\Models\User;
use DateTime;
use GuzzleHttp\Psr7\Response;
use Illuminate\Events\Dispatcher;
use Laravel\Passport\Bridge\AccessToken;
use Laravel\Passport\Bridge\AccessTokenRepository;
use Laravel\Passport\Bridge\Client;
use Laravel\Passport\Bridge\RefreshTokenRepository;
use Laravel\Passport\Bridge\Scope;
use Laravel\Passport\Passport;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;



/**
 * Trait PassportToken
 *
 * @package App\Traits
 */
trait PassportToken
{
    /**
     * Generate a new unique identifier.
     *
     * @param int $length
     *
     * @throws OAuthServerException
     *
     * @return string
     */
    private function generateUniqueIdentifier($length = 40)
    {
        try {
            return bin2hex(random_bytes($length));
            // @codeCoverageIgnoreStart
        } catch (\TypeError $e) {
            throw OAuthServerException::serverError('An unexpected error has occurred');
        } catch (\Error $e) {
            throw OAuthServerException::serverError('An unexpected error has occurred');
        } catch (\Exception $e) {
            // If you get this message, the CSPRNG failed hard.
            throw OAuthServerException::serverError('Could not generate a random string');
        }
        // @codeCoverageIgnoreEnd
    }

    private function issueRefreshToken(AccessTokenEntityInterface $accessToken)
    {
        $maxGenerationAttempts = 10;
        $refreshTokenRepository = app(RefreshTokenRepository::class);

        $refreshToken = $refreshTokenRepository->getNewRefreshToken();
        $refreshToken->setExpiryDateTime((new \DateTimeImmutable())->add(Passport::refreshTokensExpireIn()));
        $refreshToken->setAccessToken($accessToken);

        while ($maxGenerationAttempts-- > 0) {
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
            try {
                $refreshTokenRepository->persistNewRefreshToken($refreshToken);

                return $refreshToken;
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
                if ($maxGenerationAttempts === 0) {
                    throw $e;
                }
            }
        }
    }

    protected function createPassportTokenByUser(User $user, $clientId)
    {
        $accessTokenRepository = new AccessTokenRepository(new TokenRepository(), new Dispatcher());
        $accessToken = $accessTokenRepository->getNewToken(new Client($clientId, null, null), [new Scope("*")], $user->id);
        $accessToken->setIdentifier($this->generateUniqueIdentifier());
        $accessToken->setClient(new Client($clientId, null, null));
        $accessToken->setExpiryDateTime((new \DateTimeImmutable())->add(Passport::tokensExpireIn()));
        $accessTokenRepository->persistNewAccessToken($accessToken);
        $refreshToken = $this->issueRefreshToken($accessToken);


        return [
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
        ];
    }

    protected function sendBearerTokenResponse($accessToken, $refreshToken)
    {
        $response = new BearerTokenResponse();
        $response->setAccessToken($accessToken);
        $response->setRefreshToken($refreshToken);
        $privateKey = new CryptKey('file://'.Passport::keyPath('oauth-private.key'),null,false);

        $accessToken->setPrivateKey($privateKey);
        $response->setPrivateKey($privateKey);
        $response->setEncryptionKey(app('encrypter')->getKey());
        return $response->generateHttpResponse(new Response);
    }

    /**
     * @param User $user
     * @param $clientId
     * @param bool $output
     * @return mixed|\Psr\Http\Message\ResponseInterface
     */
    protected function getBearerTokenByUser(User $user, $clientId, $output = true)
    {
        $passportToken = $this->createPassportTokenByUser($user, $clientId);
        $bearerToken = $this->sendBearerTokenResponse($passportToken['access_token'], $passportToken['refresh_token']);
        if (! $output) {
            $bearerToken = json_decode($bearerToken->getBody()->__toString(), true);
        }

        return $bearerToken;
    }
}

完结

使用方法:

//获取当前用户模型
$created_user = User::find('123');
 $token =  $this->getBearerTokenByUser($created_user,config('passport.proxy.client_id'),false);
return $token;

回顾

新的版本从认证方式,到使用都有很大的变动。比如使用AccessTokenRepository而不在是new Access Token。使用DateTimeImmutable而不再是DateTime。
重写时又翻了下源码,才看到这些变更。

打赏作者

发表评论

电子邮件地址不会被公开。