分类 Laravel 下的文章

本文章旨在详解Laravel/passport的实现,并不会对对某种授权如何实现做详细说明

一、什么是Laravel Passport

passport 是一个基于Laravel实现的OAuth2服务器的扩展包,由Alex Bilbie维护的League OAuth2 server

二、OAuth和Jwt的关系

1、JWTJSON WEB TOKEN 的简写,是一种安全标准。思路是用户提供用户名和密码给服务器,服务器验证用户提交的信息合法性(是否正确),成功后返回一个令牌(Token)。后面用户就可以使用这个令牌(Token)访问服务器上的资源。

说白了,就是用户登录,成功后返回一个Token,然后用户每次请求都把Token传回服务器,服务器再用此Token得到用户的信息,判断是否有权限执行该操作。

2、OAuth 是 Open Authorization的简写,OAuth 可以说是一个协议标准,也可以说是一种框架。它详细说明了系统中不同角色、用户、服务前端应用、第三方应用、客户端之间的相互认证。但是具体的实现方式确实不固定的。

说简单点,就是包含了用户登录、第三方授权、Api授权、社交登录(用认证服务器返回的Token去请求另一个服务器的资源)的一系列方式
当然若用户想要简单的实现用户登录,需要传入更多的参数,如域、Client_ID(用来限制这是哪个客户端的请求)等等。
同样也会返回一个Token

对比来看,OAuth 就像是 JWT 的一个全方位覆盖版。

三、安装 Laravel Passport

使用Composer 包管理安装

composer require laravel/passport

四、发布 生产配置、视图、数据库等信息文件,非必须

`php artisan vendor:publish passport-config`   // 根据需要自定义的发布相应的文件

包含的有

  1. passport-components:vue 的组件
  2. passport-config : passport配置,其实就一个私钥和公钥配置
  3. passport-factories:工厂
  4. passport-migrations:数据迁移
  5. passport-views:测试时用到的界面

代码:(vendor\laravel\passport\src\PassportServiceProvider.php@boot

QQ截图20200407144510.png

五、artisan命令

注册代码继上

  1. passport:keys :生成私钥和公钥
    QQ截图20200407145425.png
  2. passport:client : 创建一个客户端

    --personal :
    QQ截图20200407145339.png

  3. passport:install :安装
    以下代码就能看出,安装其实就是生成了密钥,再创建了一个personal客户端和Password客户端

QQ截图20200407145451.png

4、passport:purge :清除已过期或已注销的

QQ截图20200407145509.png

六、路由说明

1、在AuthServiceProvider的Boot中 注册passport路由

Passport::routes(); 

2、详解oauth/token 路由
通常实现登录时,会再去请求一次oauth/token,如下,用户传入用户名和密码,服务器将信息再传入oauth/token才能获取到Token。

public function login(Request $request)
{
    $credentials = $request->validate([
        'username' => 'required|string',
        'password' => 'required|string',
        'remember_me' => 'boolean'
    ]);
   
    $http = new \GuzzleHttp\Client();
    // 发送相关字段到后端应用获取授权令牌
    $response = $http->post(config('app.url') . '/oauth/token', [
        'form_params' => [
            'grant_type' => 'password',
            'client_id' => config('services.blog.appid'),
            'client_secret' => config('services.blog.secret'),
            'username' => $credentials['username'], // 此处的username 对应数据库的email
            'password' => $credentials['password'],
            'scope' => '*'
        ],
    ]);
    return response($response->getBody());
}

现在的系统很多登录方式多种,允许email、mobile、username等多种方式,但是passport默认却是email,如何修改?
带着这个疑问去看下源码
通过 Passport:routes() 可以知道是使用Laravel\Passport\RouteRegistrar来注册的路由,最后在forAccessTokens中找到注册oauth/token的,可以看到具体的实现在 AccessTokenController@issueToken

QQ截图20200407151225.png

查看下图的AccessTokenController,可以看到在跳转到 League\OAuth2\Server\AuthorizationServer@respondToAccessTokenRequest
QQ截图20200407151444.png

League\OAuth2\Server\AuthorizationServer@respondToAccessTokenRequest实现:主要是用于判断是否为允许的授权类型

   /**
 * Return an access token response.
 *
 * @param ServerRequestInterface $request
 * @param ResponseInterface      $response
 *
 * @throws OAuthServerException
 *
 * @return ResponseInterface
 */
public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseInterface $response)
{
    foreach ($this->enabledGrantTypes as $grantType) {  // 遍历所有支持的授权类型
        if (!$grantType->canRespondToAccessTokenRequest($request)) { // 用于判断此请求是否允许的
            continue;
        }
        $tokenResponse = $grantType->respondToAccessTokenRequest(
            $request,
            $this->getResponseType(),
            $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()]
        );

        if ($tokenResponse instanceof ResponseTypeInterface) {
            return $tokenResponse->generateHttpResponse($response);
        }
    }

    throw OAuthServerException::unsupportedGrantType();
}

从上看出,最后的实现在 $grantType->respondToAccessTokenRequest中,
而$grantType 是Passport,因此对应的是
vendor\league\oauth2-server\src\Grant\PasswordGrant@respondToAccessTokenRequest

    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 access token
    $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
    $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
    $responseType->setAccessToken($accessToken);

    // Issue and persist new refresh token if given
    $refreshToken = $this->issueRefreshToken($accessToken);

    if ($refreshToken !== null) {
        $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
        $responseType->setRefreshToken($refreshToken);
    }

    return $responseType;
}

可以得到$this->validateUser($request, $client); 就是验证用户信息的,而validateUser里面会通过

  $user = $this->userRepository->getUserEntityByUserCredentials(
        $username,
        $password,
        $this->getIdentifier(),
        $client
    );

获取数据,Repository都在vendor\laravel\passport\src\Bridge中,找到UserRepository.php

public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)

{
    $provider = config('auth.guards.api.provider');

    if (is_null($model = config('auth.providers.'.$provider.'.model'))) {
        throw new RuntimeException('Unable to determine authentication model from configuration.');
    }

    if (method_exists($model, 'findAndValidateForPassport')) {
        $user = (new $model)->findAndValidateForPassport($username, $password);

        if (! $user) {
            return;
        }

        return new User($user->getAuthIdentifier());
    }

    if (method_exists($model, 'findForPassport')) {
        $user = (new $model)->findForPassport($username);
    } else {
        $user = (new $model)->where('email', $username)->first();
    }

    if (! $user) {
        return;
    } elseif (method_exists($user, 'validateForPassportPasswordGrant')) {
        if (! $user->validateForPassportPasswordGrant($password)) {
            return;
        }
    } elseif (! $this->hasher->check($password, $user->getAuthPassword())) {
        return;
    }

    return new User($user->getAuthIdentifier());
}

可以看到实现,知道了在User模型中加上对应方法可以实现不同的功能
findAndValidateForPassport:会传入username,password。可以自定义验证,包括密码的加密方式
findForPassport:会传入username,可以用于用户名的验证,默认email
validateForPassportPasswordGrant: 可以用于密码的验证方式,默认hash

前言

官方文档
参考了PHP 开发环境安装配置:Win10+Docker+Laradock(上篇)
Laradock 是Laravel + docker 的缩写,最早是为Laravel打造的,现在已成为广泛使用的一套基于Docker的PHP开发环境,提供了多个预先配置和打包的Docker镜像。
类似的选择有 Homestead、Laragon.
安装准备:

  • Win10 x64 专业版 (Win10 才用Hyper-V,因此不支持虚拟机版Win10安装)
  • **BIOS 开启 虚拟化(Virtualization is enabled)
  • CPU支持SLAT功能
  • 请退出360 安全卫士等软件
  • Docker 安装程序 链接:https://pan.baidu.com/s/1loaiILSMxtKzuMco6crKtA 提取码:n7en

<span style='color:red'>注意,安装前请确认没有安装过虚拟机软件,如VMware Workstation 、Virtual Box。如果安装过一定要将其卸载,否则可能会出现冲突,因为Win10 安装Docker时会启用Win10自带的Hyper-V虚拟机

Win10 中安装 Docker

  1. 运行下载好的 Docker Desktop Installer.exe安装程序,按下图选择。
    Add shortcut to desktop 是指添加桌面快捷方式
    Use Windows containers instead... 是指使用Windows容器而不是Linux容器(可以在安装后更改),不要勾选,因为Laradock需要Linux安装
    7P(((TQDR[L`VL7B$~$)C~V.png
  2. 正在安装

正在安装.png

  1. 安装完成,点击 Close and log out(关闭并注销),会注销系统。

    安装完成.png

  1. 重新登录账户后,会弹出如下提示,点击OK!意思是Hyper-V未启用,Docker不能正常工作,重启启动Hyper-V!

    4.png

  2. 重启成功后,通知栏会出现一个Docker的图标,显示Docker desktop is runing,说明 Docker已成功运行。
    右键Settings->Advanced,配置虚拟机的信息(CPU,内存)