方法 Illuminate\Auth\RequestGuard::logout 不存在 Laravel Passport

Posted

技术标签:

【中文标题】方法 Illuminate\\Auth\\RequestGuard::logout 不存在 Laravel Passport【英文标题】:Method Illuminate\Auth\RequestGuard::logout does not exist Laravel Passport方法 Illuminate\Auth\RequestGuard::logout 不存在 Laravel Passport 【发布时间】:2020-01-08 20:19:22 【问题描述】:

我使用 Laravel Passport 构建 API,我相应地删除了网络路由及其保护

如何测试用户注销?

这是我目前所拥有的:

Logout Test

/**
 * Assert users can logout
 *
 * @return void
 */
public function test_logout()

    // $data->token_type = "Bearer"
    // $data->access_token = "Long string that is a valid token stripped out for brevety"
    $response = $this->json('POST', '/api/logout', [], [
         'Authorization' => $data->token_type . ' ' . $data->access_token
    ]);
    $response->assertStatus(200);

routes/api.php

Route::post('logout', 'Auth\LoginController@logout')->name('logout');
控制器方法使用 AuthenticatesUsers 特征,因此保留默认功能
/**
 * Log the user out of the application.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function logout(Request $request)

    $this->guard()->logout();

    $request->session()->invalidate();

    return $this->loggedOut($request) ?: redirect('/');

错误方法 Illuminate\Auth\RequestGuard::logout 不存在

Laravel Documentation 谈到了颁发和刷新访问令牌,但没有谈到撤销它们或执行注销

注意:我正在使用密码授权令牌

注意 2:撤销用户的令牌不起作用

public function logout(Request $request)

    $request->user()->token()->revoke();
    return $this->loggedOut($request);

Test Fails on second assertion

public function test_logout()

    $response = $this->json('POST', '/api/logout', [], [
         'Authorization' => $data->token_type . ' ' . $data->access_token
    ]);
    $response->assertStatus(200); // Passes
    $check_request = $this->get('/api/user');
    $check_request->assertForbidden(); // Fails

给定需要身份验证的默认路由

Route::middleware('auth:api')->get('/user', function (Request $request) 
    return $request->user();
);

响应状态码 [200] 不是禁止状态码。

那么发生了什么?以及如何使用 Passport 测试用户注销?

提前致谢

【问题讨论】:

【参考方案1】:

正在撤销令牌。是测试不工作,但原因不明显。

在一次测试中发出多个请求时,您的 laravel 应用程序的状态不会在请求之间重置。 Auth 管理器是 laravel 容器中的一个单例,它保留已解析的 auth 守卫的本地缓存。已解析的身份验证守卫保留身份验证用户的本地缓存。

因此,您对api/logout 端点的第一个请求解析了身份验证管理器,该管理器解析了 api 防护,它存储了对您将撤销其令牌的经过身份验证的用户的引用。

现在,当您向/api/user 发出第二个请求时,已从容器中提取已解析的身份验证管理器,已从其本地缓存中提取已解析的 api 防护,并从防护的中提取相同的已解析用户本地缓存。这就是第二个请求通过身份验证而不是失败的原因。

在同一测试中使用多个请求测试与身份验证相关的内容时,您需要在测试之间重置已解析的实例。此外,您不能只取消设置已解析的身份验证管理器实例,因为当它再次解析时,它不会定义扩展的 passport 驱动程序。

所以,我发现最简单的方法是使用反射来取消设置已解析身份验证管理器上受保护的 guards 属性。您还需要在已解析的会话保护上调用logout 方法。

我的 TestCase 类上有一个方法,类似于:

protected function resetAuth(array $guards = null)

    $guards = $guards ?: array_keys(config('auth.guards'));

    foreach ($guards as $guard) 
        $guard = $this->app['auth']->guard($guard);

        if ($guard instanceof \Illuminate\Auth\SessionGuard) 
            $guard->logout();
        
    

    $protectedProperty = new \ReflectionProperty($this->app['auth'], 'guards');
    $protectedProperty->setAccessible(true);
    $protectedProperty->setValue($this->app['auth'], []);

现在,您的测试将如下所示:

public function test_logout()

    $response = $this->json('POST', '/api/logout', [], [
         'Authorization' => $data->token_type . ' ' . $data->access_token
    ]);
    $response->assertStatus(200);

    // Directly assert the api user's token was revoked.
    $this->assertTrue($this->app['auth']->guard('api')->user()->token()->revoked);

    $this->resetAuth();

    // Assert using the revoked token for the next request won't work.
    $response = $this->json('GET', '/api/user', [], [
         'Authorization' => $data->token_type . ' ' . $data->access_token
    ]);
    $response->assertStatus(401);

【讨论】:

完美的解释,不过,中间件返回的是 401 而不是 403,所以断言应该是 $response->assertUnauthorized(); @CaddyDZ 很好。我刚刚从您的原始代码中复制了该断言,并没有考虑更新它。我刚刚通过直接状态代码检查更新了我的答案,无论如何我更喜欢使用它。很高兴我能提供帮助。 它给了我错误ErrorException: Undefined property: $app in file。我正在使用 Laravel 8 @fahadshaikh 你的测试类是否扩展了Tests\TestCase

以上是关于方法 Illuminate\Auth\RequestGuard::logout 不存在 Laravel Passport的主要内容,如果未能解决你的问题,请参考以下文章

init()方法和构造方法的区别

GroovyGroovy 扩展方法 ( 扩展静态方法示例 | 扩展实例方法示例 | 扩展实例方法与扩展静态方法代码相同 )

Python中静态方法和类方法的区别

类方法和实例方法

Java中的构造方法与普通方法的区别? 啥情况下用构造方法啥情况下用普通的方法

Python中静态方法和类方法的区别