我们如何在Laravel中实现仅自定义API的身份验证 [英] How do we implement custom API-only authentication in Laravel

查看:103
本文介绍了我们如何在Laravel中实现仅自定义API的身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这不是一个非常需要答案的问题,但是欢迎进一步的建议以及答案和建议.我想与全世界分享我如何解决此问题,并希望它对其他人有帮助.

This isn't a question so much in need of an answer, but further suggestions and answers and recommendations are welcome. I want to share with the world how I resolved this issue and hope it helps others.

Laravel附带了一些预先设计的身份验证解决方案,您可以使用一些手工艺的命令来启动它们.这些包括:

Laravel comes with several pre-designed authentication solutions that you can spin up with a few artisan commands. These include:

  • 标准用户表身份验证
  • OAuth2(通过Laravel Passport软件包)
  • 基于社交媒体的身份验证(通过Laravel Socialite软件包)

尽管所有这些功能都很有用,但在当今的微服务时代,Laravel并没有提供很多使用自定义API的仅API身份验证的现成引导程序.

As useful as all of these are, in this age of micro-services, Laravel doesn't provide much in the way of an out-of-the-box bootstrap for API-only authentication using custom APIs.

几个月前,我遇到了这个问题,我在Google和Stackoverflow上搜索了答案.我找到了有用的文章,这些文章有助于指明方向,并引用了这些文章.花了些力气才能了解如何将它们粘合在一起并逐步调试以解决问题.

I was faced with this problem several months ago and I searched Google and Stackoverflow for an answer. I found helpful articles which helped to point the way, and these are cited. It took some effort to understand how to glue them together and step-debugging to iron out the kinks.

提供答案是希望对其他人和我自己有所帮助,在将来我不得不再次做同样的事情.

The answer is provided in the hope that it helps others - and myself, where I have to do the same thing again in the future.

假设和范围:

  • you've created your own API like https://example.com/login and https://example.com/logout
  • you're running a website that requires authentication, but not via models and tables or social media
  • your API manages interactions with tables, including user-login/logout

推荐答案

该解决方案涉及七个PHP文件

  • app/Http/Controllers/HomeController.php-主页控制器;经过身份验证的用户的目的地
  • app/Providers/ApiUserProvider.php-用于引导和注册登录用户的自定义提供程序,并实现Illuminate \ Contracts \ Auth \ UserProvider接口
  • app/CoreExtensions/SessionGuardExtended.php-用于登录用户并接收身份验证值并将其存储在会话数组中的自定义守护程序控制器;扩展了Illuminate \ Auth \ SessionGuard类
  • app/ApiUser-如果您使用的是OAuth2(Laravel的Passport);公开OAuth access_token的自定义用户类;扩展Illuminate \ Auth \ GenericUser并实现Illuminate \ Contracts \ Auth \ Authenticatable接口
  • config/auth.php-指示Auth()外观返回自定义会话防护的auth配置
  • app/Providers/AuthServiceProvider.php-身份验证引导程序
  • app/Providers/AppServiceProvider.php-主应用程序引导程序
  • The solution involves seven PHP files

    • app/Http/Controllers/HomeController.php - homepage controller; the destination for an authenticated user
    • app/Providers/ApiUserProvider.php - a custom provider to bootstrap and register the logged-in user, and implements the interface Illuminate\Contracts\Auth\UserProvider
    • app/CoreExtensions/SessionGuardExtended.php - custom guard-controller to log-in the user and receives the authentication values and stores them in session array; extends class Illuminate\Auth\SessionGuard
    • app/ApiUser - if you're using OAuth2 (Laravel's Passport); custom user class that exposes the OAuth access_token; extends Illuminate\Auth\GenericUser and implements the interface Illuminate\Contracts\Auth\Authenticatable
    • config/auth.php - the auth config which instructs the Auth() facade to return the custom session guard
    • app/Providers/AuthServiceProvider.php - the auth bootstrap
    • app/Providers/AppServiceProvider.php - the main application bootstrap
    • 引用源研究/调查材料供您自己调查并了解其存在的背景.我没有声称自己是一个天才,他通过我自己的魔力从零开始创造了解决方案,但是,像所有创新者一样,我是在别人的努力下建立的.我文章的独特卖点是,我提供了完整的打包解决方案,而引用的资源则为整体答案的小众部分提供了解决方案.经过多次尝试和错误,他们一起帮助我形成了一个完整的解决方案.

      Source research/investigation material are cited for you to investigate for yourself and comprehend the background context to their existence. I make no claims to be a genius who created the solution from scratch through my own mojo, but rather that - like all innovators - I build on the efforts of others. The unique selling point of my article is that I provide a complete packaged solution, whereas the cited sources provide solutions to niche parts of the overall answer. Together, after much trial and error, they helped me to form a complete solution.

      了解a config/auth.php如何影响AuthManager.php中的执行的非常有用的文章是

      A really useful article to understands how config/auth.php affects execution in AuthManager.php is https://www.2hatslogic.com/blog/laravel-custom-authentication/

      未对以下内容进行任何代码修改,但包含这些内容是为了承认它们在此过程中所扮演的角色及其重要性:

      No code modifications are made to the following, but they're included to acknowledge the role they play and their importance in the process:

      • vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php-主要授权工厂经理
      • Auth()门面-默认情况下返回收缩包装的Illuminate \ Auth \ SessionGuard类实例,除非通过config/auth.php文件指示进行其他操作-整个Laravel代码中普遍使用Auth()来检索会话警卫
      <?php
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      
      /**
       * Handles and manages the home-page
       * 
       * @category controllers
       */
      class HomeController extends Controller
      {
          /**
           * Create a new controller instance.
           *
           * @return void
           */
          public function __construct()
          {
              $this->middleware('auth');
          }
      
          public function index()
          {
              blah
          }
      
          ... other methods ... 
      
      }
      

      app/Providers/ApiUserProvider.php

      来源:

      • Using Laravel 5.8 authentication with external JSON API (Creating own ServiceProvider)
      • https://laracasts.com/discuss/channels/laravel/replacing-the-laravel-authentication-with-a-custom-authentication
      • Custom user authentication base on the response of an API call
      <?php
      namespace App\Providers;
      
      use Illuminate\Contracts\Auth\UserProvider;
      use Illuminate\Contracts\Auth\Authenticatable as UserContract;
      use App\ApiUser;
      
      /**
       * Delegates API user login and authentication
       * 
       * @category providers
       */
      class ApiUserProvider implements UserProvider
      {
          
          /**
           * Custom API Handler 
           * Used to request API and capture responses
           * 
           * @var \Path\To\Your\Internal\Api\Handler
           */
          private $_oApi = null;
          
          /**
           * POST request to API
           * 
           * @param string  $p_url      Endpoint URL
           * @param array   $p_arrParam Parameters
           * @param boolean $p_isOAuth2 Is OAuth2 authenticated request? [Optional, Default=True]
           * 
           * @return array
           */
          private function _post(string $p_url, array $p_arrParam, bool $p_isOAuth2=true)
          {
              if (!$this->_oApi) {
                  $this->_oApi = new \Path\To\Your\Internal\Api\Handler();
              }
              $arrResponse = $this->_oApi->post($p_url, $p_arrParam, $p_isOAuth2);
              return $arrResponse;
          }
          
          /**
           * GET request to API
           * 
           * @param string $p_url     Endpoint URL
           * @param array $p_arrParam Parameters [Optional, Default = array()]
           * 
           * @return array
           */
          private function _get(string $p_url, array $p_arrParam=[], bool $p_isOAuth2=true)
          {   
              if (!$this->_oApi) {
                  $this->_oApi = new \Path\To\Your\Internal\Api\Handler();
              }
              $arrResponse = $this->_oApi->get($p_url, $p_arrParam);
              return $arrResponse;
          }
          
          /**
           * Retrieve a user by the given credentials.
           *
           * @param array $p_arrCredentials
           * 
           * @return \Illuminate\Contracts\Auth\Authenticatable|null
           */
          public function retrieveByCredentials(array $p_arrCredentials)
          {
              $arrResponse = $this->_post('/login', $p_arrCredentials, false);
              if ( $arrResponse['result'] ) {
                  $arrPayload = array_merge(
                      $arrResponse['data'],
                      $p_arrCredentials
                  );
                  return $this->getApiUser($arrPayload);
              }
          }
      
          /**
           * Retrieve a user by their unique identifier.
           *
           * @param mixed $p_id
           * 
           * @return \Illuminate\Contracts\Auth\Authenticatable|null
           */
          public function retrieveById($p_id)
          {
              $arrResponse = $this->_get("user/id/{$p_id}");        
              if ( $arrResponse['result'] ) {
                  return $this->getApiUser($arrResponse['data']);
              }
          }
      
          /**
           * Validate a user against the given credentials.
           *
           * @param \Illuminate\Contracts\Auth\Authenticatable $p_oUser
           * @param array                                      $p_arrCredentials
           * 
           * @return bool
           */
          public function validateCredentials(UserContract $p_oUser, array $p_arrCredentials)
          {
              return $p_oUser->getAuthPassword() == $p_arrCredentials['password'];
          }
      
          /**
           * Get the api user.
           *
           * @param mixed $p_user
           * 
           * @return \App\Auth\ApiUser|null
           */
          protected function getApiUser($p_user)
          {
              if ($p_user !== null) {
                  return new ApiUser($p_user);
              }
              return null;
          }
      
          protected function getUserById($id)
          {
              $user = [];
      
              foreach ($this->getUsers() as $item) {
                  if ($item['account_id'] == $id) {
                      $user = $item;
      
                      break;
                  }
              }
      
              return $user ?: null;
          }
      
          protected function getUserByUsername($username)
          {
              $user = [];
      
              foreach ($this->getUsers() as $item) {
                  if ($item['email_address'] == $username) {
                      $user = $item;
      
                      break;
                  }
              }
      
              return $user ?: null;
          }
          
      
          /**
           * The methods below need to be defined because of the Authenticatable contract
           * but need no implementation for 'Auth::attempt' to work and can be implemented
           * if you need their functionality
           */
          public function retrieveByToken($identifier, $token) { }
          public function updateRememberToken(UserContract $user, $token) { }
          
      }
      

      app/CoreExtensions/SessionGuardExtended.php

      来源:

      • Extending Laravel 5.2 SessionGuard
      • Using Laravel 5.8 authentication with external JSON API (Creating own ServiceProvider)
      <?php
      namespace App\CoreExtensions;
      
      use Illuminate\Auth\SessionGuard;
      use Illuminate\Contracts\Auth\Authenticatable;
      
      /**
       * Extended SessionGuard() functionality 
       * Provides added functionality to store the OAuth tokens in the session for later use
       * 
       * @category guards
       * 
       * @see https://stackoverflow.com/questions/36087061/extending-laravel-5-2-sessionguard
       */
      class SessionGuardExtended extends SessionGuard
      {
          
          /**
           * Log a user into the application.
           *
           * @param  \Illuminate\Contracts\Auth\Authenticatable  $p_oUser
           * @param  bool  $p_remember
           * @return void
           */
          public function login(Authenticatable $p_oUser, $p_remember = false)
          {
              
              parent::login($p_oUser, $p_remember);
              
              /**
               * Writing the OAuth tokens to the session
               */
              $key = 'authtokens';
              $this->session->put(
                  $key, 
                  [
                      'access_token' => $p_oUser->getAccessToken(),
                      'refresh_token' => $p_oUser->getRefreshToken(),
                  ]
              );
          }
          
          /**
           * Log the user out of the application.
           *
           * @return void
           */
          public function logout()
          {
              parent::logout();
              
              /**
               * Deleting the OAuth tokens from the session
               */
              $this->session->forget('authtokens');        
          }
          
      }
      

      app/ApiUser

      来源:

      • Using Laravel 5.8 authentication with external JSON API (Creating own ServiceProvider) *https://laracasts.com/discuss/channels/laravel/replacing-the-laravel-authentication-with-a-custom-authentication
      • Custom user authentication base on the response of an API call
      <?php
      namespace App;
      
      use Illuminate\Auth\GenericUser;
      use Illuminate\Contracts\Auth\Authenticatable as UserContract;
      
      class ApiUser extends GenericUser implements UserContract
      {
          
          /**
           * Returns the OAuth access_token
           * 
           * @return mixed
           */
          public function getAccessToken()
          {
              return $this->attributes['access_token'];
          }
          
          
          public function getRefreshToken()
          {
              return $this->attributes['refresh_token'];
          }
          
      }
      

      app/Providers/AuthServiceProvider.php

      <?php
      namespace App\Providers;
      
      use Illuminate\Support\Facades\Auth;
      use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
      
      class AuthServiceProvider extends ServiceProvider
      {
          
          /**
           * Register any authentication / authorization services.
           *
           * @return void
           */
          public function boot()
          {
              $this->registerPolicies();
              
              Auth::provider('frank_sinatra', function ($app, array $config) {
                  // Return an instance of Illuminate\Contracts\Auth\UserProvider...
      
                  return new ApiUserProvider();
              });
              
          }
      }
      
      

      app/Providers/AppServiceProvider.php

      来源:

      注意:

      关于此PHP文件中的编码更改,存在一些细微的问题. 如果您想了解更多,请查看vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php,尤其是AuthManager :: resolve().

      There is a couple of nuanced issues regarding the change to coding in this PHP file. If you want to understand more, look at vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php, AuthManager::resolve() in particular.

      1. 对config/auth.php'session'和'token'的引用由硬编码方法AuthManager :: createSessionDriver()和AuthManager :: createTokenDriver()提供. (如果您知道在应用程序中扩展AuthManager.php的方法,请告诉我)
      2. AppServiceProvider.php可以解救!可以在AppServiceProvider :: boot()中注册自定义防护,并在执行默认代码之前对其进行拦截.
      3. 我对上面的要点2表示满意,但是我们不能做些聪明的事情,例如从AppServiceProvider返回自定义会话防护名称或实例,在专门的公共方法中设置setCookieJar(),setDispatcher(),setRequest()在AuthManager.php中,可以将其挂接到AppServiceProvider.php或由config/auth.php驱动,以便在AuthManager.php中创建自定义会话防护后执行?
      4. 没有cookie或会话,就不会通过重定向保留用户的身份.解决此问题的唯一方法是在我们当前的解决方案中的AppServiceProvider中包含setCookieJar(),setDispatcher()和setRequest().
      1. References to config/auth.php 'session' and 'token' are served by hard-coded methods AuthManager::createSessionDriver() and AuthManager::createTokenDriver() (Tell me please if you know of a way to extend AuthManager.php in the app)
      2. AppServiceProvider.php to the rescue! Custom guards can be registered in AppServiceProvider::boot() and intercepted before the default code can be executed.
      3. I'm OK with point 2 above, but couldn't we do something clever like return the custom session-guard name or instance from AppServiceProvider, have setCookieJar(), setDispatcher(), setRequest() in a specialized public method in AuthManager.php, which can be hooked into AppServiceProvider.php or driven by config/auth.php to execute after creating the custom session-guard in AuthManager.php?
      4. Without the cookies or sessions, the user's identity isn't preserved through the redirect. The only way to resolve this is to include the setCookieJar(), setDispatcher() and setRequest() in AppServiceProvider within our current solution.

      <?php
      namespace App\Providers;
      
      use Illuminate\Support\ServiceProvider;
      use Illuminate\Support\Facades\Auth;
      use App\CoreExtensions\SessionGuardExtended;
      
      class AppServiceProvider extends ServiceProvider
      {
          /**
           * Register any application services.
           *
           * @return void
           */
          public function register()
          {
              //
          }
      
          /**
           * Bootstrap any application services.
           * 
           * @see https://stackoverflow.com/questions/36087061/extending-laravel-5-2-sessionguard
           *
           * @return void
           */
          public function boot()
          {
              
              /**
               * Extending Illuminate\Auth\SessionGuard()
               * This is so we can store the OAuth tokens in the session
               */
              Auth::extend(
                  'sessionExtended',
                  function ($app) {
                  
                      $guard = new SessionGuardExtended(
                          'sessionExtended', 
                          new ApiUserProvider(), 
                          app()->make('session.store'),
                          request()
                      );
                  
                      // When using the remember me functionality of the authentication services we
                      // will need to be set the encryption instance of the guard, which allows
                      // secure, encrypted cookie values to get generated for those cookies.
                      if (method_exists($guard, 'setCookieJar')) {
                          $guard->setCookieJar($this->app['cookie']);
                      }
      
                      if (method_exists($guard, 'setDispatcher')) {
                          $guard->setDispatcher($this->app['events']);
                      }
      
                      if (method_exists($guard, 'setRequest')) {
                          $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
                      }
      
                      return $guard;
                  }
              );
          }
      }
      

      config/auth.php

      来源:

      <?php
      
      return [
      
          /*
          |--------------------------------------------------------------------------
          | Authentication Defaults
          |--------------------------------------------------------------------------
          |
          | This option controls the default authentication "guard" and password
          | reset options for your application. You may change these defaults
          | as required, but they're a perfect start for most applications.
          |
          */
      
          'defaults' => [
              //'guard' => 'web', /** This refers to the settings under ['guards']['web'] */
              'guard' => 'webextended', /** This refers to the settings under ['guards']['webextended'] */
              'passwords' => 'users', /** This refers to the settings under ['passwords']['users'] */
          ],
      
          /*
          |--------------------------------------------------------------------------
          | Authentication Guards
          |--------------------------------------------------------------------------
          |
          | Next, you may define every authentication guard for your application.
          | Of course, a great default configuration has been defined for you
          | here which uses session storage and the Eloquent user provider.
          |
          | All authentication drivers have a user provider. This defines how the
          | users are actually retrieved out of your database or other storage
          | mechanisms used by this application to persist your user's data.
          |
          | Supported: "session", "token"
          |
          */
      
          'guards' => [
              'web' => [
                  'driver' => 'session', /** This refers to Illuminate/Auth/SessionGuard */
                  'provider' => 'users', /** This refers to the settings under ['providers']['users'] */
              ],
              
              'webextended' => [
                  'driver' => 'sessionExtended', /** @see app/Providers/AppServiceProvider::boot() */
                  'provider' => 'users', /** This refers to the settings under ['providers']['users'] */
              ],
      
              'api' => [
                  'driver' => 'token', /** This refers to Illuminate/Auth/TokenGuard */
                  'provider' => 'users',
                  'hash' => false,
              ],
          ],
      
          /*
          |--------------------------------------------------------------------------
          | User Providers
          |--------------------------------------------------------------------------
          |
          | All authentication drivers have a user provider. This defines how the
          | users are actually retrieved out of your database or other storage
          | mechanisms used by this application to persist your user's data.
          |
          | If you have multiple user tables or models you may configure multiple
          | sources which represent each model / table. These sources may then
          | be assigned to any extra authentication guards you have defined.
          |
          | Supported: "database", "eloquent"
          |
          */
      
          'providers' => [
              'users' => [
                  'driver' => 'frank_sinatra',  /** @see app/Providers/AuthServiceProvider::boot() */
                  //'model' => App\User::class,
              ],
      
              // 'users' => [
              //     'driver' => 'database',
              //     'table' => 'users',
              // ],
          ],
      
          [
              blah
          ],
      
          [
              other settings
          ],
      
      ];
      
      

      如何使用此解决方案

      非常简单.整体方法没有变化.换句话说,我们使用Auth()门面.

      How To Use This Solution

      Very simple. There's no change in the overall approach. In other words, we use the Auth() facade.

      使用自定义API /login?username=<username>&password=<password>

      request()->flash();
      $arrData = request()->all();
      
      if ( Auth::attempt($arrData, true) ) {
          return redirect('home');
      } else  {
          return back()->withErrors(
              [
                  'username' => "Those credentials can't be found",
                  'password' => "Those credentials can't be found",
              ]
          );
      }
      

      使用自定义API /logout

      Auth::logout();
      return redirect('home');
      

      这篇关于我们如何在Laravel中实现仅自定义API的身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆