CakePHP混合使用登录重定向和无状态身份验证器

ozxc1zmp  于 2022-11-12  发布在  PHP
关注(0)|答案(1)|浏览(145)

我尝试让我们的CakePHP v4应用程序在新的身份验证插件中同时使用有状态的身份验证器(如用户名/密码)和无状态的身份验证器(如Basic)。它们似乎不能有效地一起使用, AJAX 请求在不使用无状态的身份验证器的情况下也能得到适当的响应。
我有一个应用程序,它既可以作为常规浏览器应用程序 *,也可以作为通过 AJAX 提供JSON的API *。但是,我正在努力配置身份验证,以同时满足这两种使用情况。

我需要未经验证的API和 AJAX 请求,其中包含401 HTTP代码,但需要未经验证的浏览器请求重定向到登录URL。

我已经阅读了关于混合它们的文档,但是根据结果的行为对浏览器用户没有用处。

$service = new AuthenticationService();

    // Define where users should be redirected to when they are not authenticated
    $service->setConfig([
        'unauthenticatedRedirect' => '/users/login',
        'queryParam' => 'redirect',
    ]);

    // Load identifiers
    $service->loadIdentifier('Authentication.Password');

    // Load the authenticators leaving Basic as the last one.
    $service->loadAuthenticator('Authentication.Session');
    $service->loadAuthenticator('Authentication.Form');
    $service->loadAuthenticator('Authentication.HttpBasic');

如果新用户在浏览器(例如www.example.com/test)中导航到应用程序,则不会重定向他们,也不会向他们提供登录页面HTML,但会向他们发出质询响应(由于HttpBasicAuthenticator中引发的异常),浏览器将弹出内置用户名/密码表单之一,例如:

使用后端HTTP客户端的未经身份验证的API用户将获得正确的401,但浏览器体验是错误的。
浏览器 AJAX 请求也仍然存在问题,即使您只是使用会话cookie等纯状态身份验证器,因为现在当用户无法通过身份验证时,所有请求都将以302重定向到登录URL的方式响应,甚至AJAX类型的请求也是如此。
我们的应用确实提供HTML模板,但是内容数据是使用jQuery. AJAX 请求的,JSON响应使用标准的CakePHP数据视图和serialize键。但是,如果,例如,用户会话过期-AJAX请求将不会提供401,而是提供302 Redirect到登录页面,并使用相同的application/json Accept头。这一点,除非另行定制,将导致Cake查找JSON登录模板(例如Users/json/login.php)。
该行为可以在cURL中演示。例如:

curl -vL 'https://www.example.com/test.json' \
  -H 'Accept: application/json, text/javascript, */*; q=0.01' \
  -H 'X-Requested-With: XMLHttpRequest' 

> GET /test.json HTTP/1.1
> Host: www.example.com
> User-Agent: curl/7.64.1
> Accept: application/json, text/javascript, */*; q=0.01
> X-Requested-With: XMLHttpRequest
> 
< HTTP/1.1 302 Found
< Date: Thu, 31 Dec 2020 16:35:29 GMT
< Server: Apache/2.4.18 (Ubuntu)
< location: /users/login?redirect=%2Ftest.json
< Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS, PATCH, DELETE
< Access-Control-Allow-Headers: X-Accept-Charset,X-Accept,Content-Type,X-Requested-With
< Access-Control-Allow-Credentials: true
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
< 
> GET /users/login?redirect=%2Ftest.json HTTP/1.1
> Host: www.example.com
> User-Agent: curl/7.64.1
> Accept: application/json, text/javascript, */*; q=0.01
> X-Requested-With: XMLHttpRequest
> 
< HTTP/1.1 200 OK
< Date: Thu, 31 Dec 2020 16:35:31 GMT
< Server: Apache/2.4.18 (Ubuntu)
< Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS, PATCH, DELETE
< Access-Control-Allow-Headers: X-Accept-Charset,X-Accept,Content-Type,X-Requested-With
< Access-Control-Allow-Credentials: true
< Content-Length: 68
< Content-Type: application/json
< 
{"message": "I wish I was just a 401 response not a Cake Template"}

我不认为REST-ful可以用302 FOUND响应资源请求并提供登录页面JSON。我们以前使用的已弃用的Auth组件可以很容易地为未验证 AJAX 和API请求401和带有302重定向的非AJAX请求提供服务,并在AuthComponent和请求检测器中使用_unauthenticated中的catch。
是否至少有某种标准的方法可以在不影响浏览器体验的情况下将有状态和无状态认证器结合起来?是否还有一种传统的方法可以让 AJAX 和API请求返回401而不是302?

2ic8powd

2ic8powd1#

分离您的API

恕我直言,这一切都不值得这么麻烦,只会造成混乱,除了增加维护复杂性外什么也不做。就我个人而言,我建议将API拆分为单独的端点,即使这意味着有一点代码重复。
当您使用单独的路由作用域时,您可以轻松地对每个路由作用域应用不同的身份验证,或者按照我通常喜欢的方式,在Application::getAuthenticationService()中构建身份验证服务时检查请求,并相应地配置服务:

$params = $request->getAttribute('params');

if ($params['prefix'] === 'Api') {
    $service->loadAuthenticator('Authentication.HttpBasic');
} else {
    $service->setConfig([
        'unauthenticatedRedirect' => '/users/login',
        'queryParam' => 'redirect',
    ]);

    $service->loadAuthenticator('Authentication.Session');
    $service->loadAuthenticator('Authentication.Form');
}

如果你真的想走这条混乱的路
一些问题的答案。
由于身份验证服务的工作方式,质询将始终存在,它将在所有身份验证器上运行,直到其中一个返回成功响应,因此,如果您未登录,您最终将到达触发质询响应的最后一个身份验证器。您可以通过使用不触发质询响应的自定义基本身份验证器来避免这种情况,请参阅**Using CakePHP Form and Basic Authentication together**。
通过将身份验证服务的unauthenticatedRedirect选项设置为null(这是默认值),可以使用异常而不是重定向,然后它将重新抛出\Authentication\Authenticator\UnauthenticatedException,它将Map到401 HTTP代码。
如果您愿意,可以根据Application::getAuthenticationService()中的请求类型动态设置该选项:

if (
    !$request->is('json') &&
    !$request->is('ajax')
) {
    $service->setConfig([
        'unauthenticatedRedirect' => '/users/login',
        'queryParam' => 'redirect',
    ]);
}

相关问题