Slim中的身份验证:是一种结合中间件、单例和钩子的智能方法


Authentication in Slim: is a combined middleware, singleton and hook approach smart?

我已经编写了自己的身份验证控制器来在Slim应用程序中执行用户身份验证。虽然它有效,但我不确定这是否是斯利姆想要的工作方式。

我的身份验证控制器$auth具有更改会话状态的$auth->login($user, $password)$auth->logout()等方法,以及报告状态的方法,如$auth->userIsLoggedIn()。此外,在给定请求的情况下,它可以确定用户是否可以访问所请求的路由。

目前,我在Slim应用程序中以两种不同的方式使用$auth的单个实例:作为注册到$app->auth的单例,以及作为应用于所有路由的路由中间件。因此,Slim应用程序是这样启动的:

// Create singleton instance of MyAuthWrapper
$app->auth = new MyAuthenticationWrapper( array() );
// Attach the same instance as middleware to all routes
$app->add( $app->auth );

我正在使用路由中的singleton实例,例如,在登录路由中:

$app->post( '/login', function() use ($app)
{
    // ...
    $user = $app->auth->authenticate( $app->request()->post('username'), $app->request()->post('password') );
    // ...
}

我在所有路由中使用中间件版本,方法是在slim.before.dispatch钩子上附加一个方法,验证用户是否通过了身份验证,否则重定向到登录页面。为了做到这一点,身份验证包装器扩展了'' Slim''中间件,从而实现了call方法,如下所示(简化):

class MyAuthenticationWrapper extends 'Slim'Middleware
{
    // ... Implementation of methods such as authenticate(), isLoggedIn(), logout(), etc.
    public function call()
    {
        $app = $this->app;
        $isAuthorized = function () use ($app) {
            $hasIdentity = $this->userIsLoggedIn(); // Assume this to work
            $isAllowed = $this->userHasAccessToRequestedRoute(); // Assume this to work
            if ($hasIdentity && !$isAllowed) 
            {
                throw new Exception("You have no access to this route");
            }
            if (!$hasIdentity && !$isAllowed) 
            {
                return $app->redirect( $loginPath );
            }
        };
        $app->hook('slim.before.dispatch', $isAuthorized);
        $this->next->call();
    }
}

对我来说,使用singleton是一种轻微的代码气味,但使用$app->add( $app->auth )将singleton实例添加为中间件会让人觉得很恶心。最后,使用中间件向调度钩子注册一个闭包,这让我怀疑对于一个名为Slim的框架来说,整个策略是否太复杂。但我不知道是否有更简单或更优雅的方式来实现我想要的。

问题:我是否走在了正确的轨道上,或者我是否错过了Slim的工作方式,这将使我能够以一种不那么复杂的方式完成这项工作?

使用中间件注册钩子进行身份验证绝对是正确的。这就是我采用的方法,也是我在自己的库Slim Auth中实现的方法。

使用Singleton肯定是一种代码气味,但并不总是如此。如果您觉得需要重构MyAuthenticationWrapper,那完全取决于您。您在自定义类中使用中间件和Hooks的方式是,IMHO,100%达到目标。

旁注:我的座右铭之一是"让它发挥作用,然后重构。"看起来你也在走上正轨,所以值得称赞。

最后,身份验证和授权是需要复杂解决方案的复杂主题。复杂并不意味着复杂、难以维护,但如果做对了,可能会产生比我希望写的更多的代码(或者比我希望通过Composer获得的依赖性更多)。

更新

如果$app->auth是中间件,那么是的,您有点偏离了轨道。你创建中间件来注册钩子的本能已经死了,但中间件就是中间件,不应该在上下文之外使用。理想情况下,您应该创建(或者最好在Packagist上找到一个包)一个身份验证类,既可以在路由中使用,也可以在中间件中使用。伪代码看起来像:

$auth = new Auth(); // This is *not* middleware
$app->auth = $auth;
// Login route looks the same
// Middleware
class MyAuthenticationWrapper extends 'Slim'Middleware
{
    public function call()
    {
        $app = $this->app;
        $auth = $app->auth;
        // Register your hook using appropriate methods from Auth class ...
        $this->next->call();
    }
}

以下是Slim Auth的中间件示例。我已经将一个示例实现放在一起,您可以查看我是如何将其放在一起的。