问题描述
这不是一个非常需要答案的问题,但欢迎进一步的建议和答案和建议.我想与全世界分享我是如何解决这个问题的,希望对其他人有所帮助.
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.
几个月前我遇到了这个问题,我在谷歌和 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.
假设和范围:
- 您已经创建了自己的 API,例如 https://example.com/login 和 https://example.com/logout
- 您运行的网站需要身份验证,但不是通过模型、表格或社交媒体进行的
- 您的 API 管理与表的交互,包括用户登录/注销
- 您使用 Laravel Passport 插件进行 OAuth2 身份验证(感谢 @ShuvoJoseph 提醒我注意这一点)
推荐答案
解决方案涉及7个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() 门面返回自定义会话保护的身份验证配置
- app/Providers/AuthServiceProvider.php - 身份验证引导程序
- app/Providers/AppServiceProvider.php - 主应用程序引导
- vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php - 主要授权工厂经理
- Auth() 外观 - 默认情况下返回收缩包装的 Illuminate\Auth\SessionGuard 类实例,除非通过 config/auth.php 文件指示它执行其他操作 - Auth() 在整个 Laravel 代码中无处不在地用于检索会话守卫
引用源研究/调查材料供您自行调查并理解其存在的背景背景.我不声称自己是通过我自己的魔力从头开始创建解决方案的天才,而是像所有创新者一样,我建立在其他人的努力之上.我的文章的独特卖点是我提供了一个完整的打包解决方案,而引用的来源提供了针对整体答案的利基部分的解决方案.经过多次反复试验,他们一起帮助我形成了一个完整的解决方案.
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.
了解 config/auth.php 如何影响 AuthManager.php 中执行的非常有用的文章是 https://www.2hatslogic.com/blog/laravel-custom-authentication/
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:
<?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
来源:
- 使用 Laravel 5.8 身份验证外部 JSON API(创建自己的 ServiceProvider)
- https://laracasts.com/discuss/channels/laravel/replacing-the-laravel-authentication-with-a-custom-authentication
- 基于响应的自定义用户身份验证一个 API 调用
<?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
来源:
<?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
来源:
- 使用 Laravel 5.8 身份验证外部 JSON API(创建自己的 ServiceProvider)*https://laracasts.com/discuss/channels/laravel/replacing-the-laravel-authentication-with-a-custom-authentication
- 基于响应的自定义用户身份验证一个 API 调用
<?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.
- 对 config/auth.php 'session' 和 'token' 的引用由硬编码方法 AuthManager::createSessionDriver() 和 AuthManager::createTokenDriver() 提供(如果您知道在应用程序中扩展 AuthManager.php 的方法,请告诉我)
- AppServiceProvider.php 来救援!自定义守卫可以在 AppServiceProvider::boot() 中注册并在执行默认代码之前拦截.
- 我对上面的第 2 点没意见,但是我们不能做一些聪明的事情,例如从 AppServiceProvider 返回自定义会话保护名称或实例,在专门的公共方法中设置 setCookieJar()、setDispatcher()、setRequest()在AuthManager.php中,在AuthManager.php中创建自定义session-guard后,可以挂接到AppServiceProvider.php或者由config/auth.php驱动执行?
- 如果没有 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;
}
);
}
}
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=&password=
When logging in with your custom 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
When logging out with your custom API /logout
Auth::logout();
return redirect('home');
这篇关于我们如何在 Laravel 中实现自定义的仅限 API 的身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!