为了账号安全,请及时绑定邮箱和手机立即绑定

我们如何在 Laravel 中实现自定义 API-only 身份验证

我们如何在 Laravel 中实现自定义 API-only 身份验证

PHP
子衿沉夜 2023-04-15 17:15:07
这不是一个非常需要答案的问题,但欢迎进一步的建议、答案和建议。我想与全世界分享我是如何解决这个问题的,并希望它能帮助到其他人。Laravel 附带了几个预先设计的身份验证解决方案,您可以使用一些工匠命令启动它们。这些包括:标准用户表认证OAuth2(通过 Laravel Passport 包)基于社交媒体的身份验证(通过 Laravel Socialite 包)尽管所有这些都很有用,但在这个微服务时代,Laravel 并没有为使用自定义 API 的仅 API 身份验证提供开箱即用的引导程序。几个月前我遇到了这个问题,我在谷歌和 Stackoverflow 上搜索了答案。我找到了有助于指明方向的有用文章,并引用了这些文章。了解如何将它们粘合在一起并逐步调试以解决问题需要付出一些努力。提供答案是希望它能帮助其他人 - 以及我自己,因为我将来必须再次做同样的事情。假设和范围:您已经创建了自己的 API,例如https://example.com/login和https://example.com/logout你正在运行一个需要身份验证的网站,但不是通过模型和表格或社交媒体您的 API 管理与表的交互,包括用户登录/注销您使用 Laravel Passport 附加组件进行 OAuth2 身份验证(感谢@ShuvoJoseph 提醒我注意这一点)
查看完整描述

1 回答

?
RISEBY

TA贡献1856条经验 获得超5个赞

解决方案涉及七个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() facade 返回自定义会话保护的 auth 配置

  • app/Providers/AuthServiceProvider.php - auth bootstrap

  • app/Providers/AppServiceProvider.php - 主应用程序引导程序

引用源研究/调查材料供您自己调查并理解它们存在的背景。我并没有声称自己是一个通过自己的魔力从头开始创建解决方案的天才,而是像所有创新者一样,我建立在其他人的努力之上。我的文章的独特卖点是我提供了一个完整的打包解决方案,而引用的来源提供了整体答案的小众部分的解决方案。经过反复试验,他们一起帮助我形成了一个完整的解决方案。

了解 config/auth.php 如何影响 AuthManager.php 中的执行的真正有用的文章是https://www.2hatslogic.com/blog/laravel-custom-authentication/

未对以下内容进行任何代码修改,但将它们包括在内以确认它们所扮演的角色及其在该过程中的重要性:

  • vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php - 主要授权工厂经理

  • Auth() facade - 默认情况下返回收缩包装的 Illuminate\Auth\SessionGuard 类实例,除非通过 config/auth.php 文件指示它做其他事情 - Auth() 在整个 Laravel 代码中普遍使用来检索会话守卫

代码

应用程序/Http/Controllers/HomeController.php

<?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 ... 


}

应用程序/提供者/ApiUserProvider.php

资料来源:

<?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) { }

    

}

应用程序/CoreExtensions/SessionGuardExtended.php

资料来源:

<?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');        

    }

    

}

应用程序/ApiUser

资料来源:

<?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'];

    }

    

}

应用程序/提供商/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();

        });

        

    }

}

应用程序/提供者/AppServiceProvider.php

资料来源:

笔记:

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

  1. 对 config/auth.php 'session' 和 'token' 的引用由硬编码方法 AuthManager::createSessionDriver() 和 AuthManager::createTokenDriver() 提供(如果您知道扩展 AuthManager.php 的方法请告诉我应用程序)

  2. AppServiceProvider.php 来拯救!可以在 AppServiceProvider::boot() 中注册自定义守卫,并在执行默认代码之前拦截。

  3. 我同意上面的第 2 点,但我们不能做一些聪明的事情,比如从 AppServiceProvider 返回自定义会话保护名称或实例,在 AuthManager 的专用公共方法中设置 setCookieJar()、setDispatcher()、setRequest()。 php,它可以挂接到 AppServiceProvider.php 或由 config/auth.php 驱动在 AuthManager.php 中创建自定义会话保护后执行?

  4. 如果没有 cookie 或会话,则不会通过重定向保留用户的身份。解决此问题的唯一方法是在我们当前的解决方案中的 AppServiceProvider 中包含 setCookieJar()、setDispatcher() 和 setRequest()。

<?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;

            }

        );

    }

}

配置/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() 门面。


使用自定义 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');


查看完整回答
反对 回复 2023-04-15
?
汪汪一只猫

TA贡献1898条经验 获得超8个赞

这种方法的问题是它不处理密码重置,这需要将令牌存储在本地数据库中并且很难覆盖。



查看完整回答
反对 回复 2023-04-15
  • 1 回答
  • 0 关注
  • 111 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信