Laravel 5.6 上的 Ajax 身份验证重定向

Posted

技术标签:

【中文标题】Laravel 5.6 上的 Ajax 身份验证重定向【英文标题】:Ajax Auth redirect on Laravel 5.6 【发布时间】:2018-11-25 04:24:50 【问题描述】:

我有一个触发 Ajax Post 的 Like 按钮,该路由受 auth:api 中间件保护:

我的项目/路由/api.php

Route::group(['middleware' => ['auth:api']], function () 
    Route::post('/v1/like', 'APIController@set_like');
);

当一个经过身份验证的用户点击like按钮时,一点问题都没有,一切都很顺利。但是当客人点击按钮时,我通过 javascript 将他们重定向到登录页面,并且在身份验证后,他们被重定向到 RedirectIfAuthenticated 中间件中指定的页面,因此通常是 /home。 我对该中间件进行了如下修改:

myproject/app/Http/Middleware/RedirectIfAuthenticated.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    
        if (Auth::guard($guard)->check()) 
            return redirect()->intended('/home');
        
        return $next($request);
    

我的 Ajax 调用是这样的:

var toggleLike = function()
    var token = USER_TOKEN; //javascript variable
    var current_type = $(thisLikeable).attr("data-type");
    var current_id = $(thisLikeable).attr("data-id");
    var jqxhr = $.post( APP_URL + "/api/v1/like", api_token: token, type: current_type, id: current_id, function(data) 
        setLikeAppearance(data.message);
    )
    .fail(function(xhr, status, error)
        if (xhr.status == 401) 
            window.location = APP_URL + "/login" ;
        
    );
;

这里的问题是预期的()函数,对于 Ajax 调用,它没有存储正确的会话变量,我不知道如何正确设置它。 我显然遗漏了一些明显的东西,有人可以帮忙吗? 干杯!

编辑

我想要实现的是:

    GUEST 在 //mysite/blabla 点击喜欢按钮 被重定向到登录 登录(或注册) 被重定向到 //mysite/blabla 并且 Like 已经被触发

【问题讨论】:

api 上不会有会话。因为它是无状态的。 是的,但必须有办法处理这个.. 只需从 api 发送 401 响应。比你需要从前端处理它 不要从 api 重定向。 【参考方案1】:

发生的情况是,在 API 中会话不受管理,或者换句话说,它是无状态的。所以会话中间件并没有在 Laravel 框架上实现 API 请求。虽然可以手动添加,但也不是闲着用。因此,如果 API 不使用会话并使用重定向,fronted 不知道它,因为 API 和前端作为两个独立的应用程序工作。因此,您需要向前端发送响应的状态,并让前端像使用 ajax 一样处理重定向。如果未经身份验证,只需删除重定向并让 API 抛出未经授权的异常。然后,从处理程序,处理未经授权的异常。

这里是怎么做的。

将此添加到 app/Exceptions/Handler.php

/**
 * Convert an authentication exception into an unauthenticated response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Auth\AuthenticationException  $exception
 * @return \Illuminate\Http\Response
 */
protected function unauthenticated($request, AuthenticationException $exception)

    if ($request->expectsJson()) 
        return response()->json(['error' => 'Unauthenticated.'], 401);
    

    return redirect()->guest('login');

如果请求是 json(api 请求),这将向用户发送 401 消息未验证,否则(如果 web 请求)将重定向到登录

检查下面的render 方法或从source 检查它以了解发生了什么。当抛出未经授权的异常时,我们会告诉检查请求类型,如果它来自 API 请求,我们将发送带有 401 状态代码的 json 响应。所以从前端知道我们可以在看到 401 状态码后将用户重定向到登录页面。

来源

/**
 * Render an exception into a response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $e
 * @return \Symfony\Component\HttpFoundation\Response
 */
public function render($request, Exception $e)

    if (method_exists($e, 'render') && $response = $e->render($request)) 
        return Router::toResponse($request, $response);
     elseif ($e instanceof Responsable) 
        return $e->toResponse($request);
    
    $e = $this->prepareException($e);
    if ($e instanceof HttpResponseException) 
        return $e->getResponse();
     elseif ($e instanceof AuthenticationException) 
        return $this->unauthenticated($request, $e);
     elseif ($e instanceof ValidationException) 
        return $this->convertValidationExceptionToResponse($e, $request);
    
    return $request->expectsJson()
                    ? $this->prepareJsonResponse($request, $e)
                    : $this->prepareResponse($request, $e);

编辑后

预期的method() 仅用于网络路由,因为它使用会话来提取预期的路由或手动传递的值。这是预期的()方法。

/**
 * Create a new redirect response to the previously intended location.
 *
 * @param  string  $default
 * @param  int     $status
 * @param  array   $headers
 * @param  bool    $secure
 * @return \Illuminate\Http\RedirectResponse
 */
public function intended($default = '/', $status = 302, $headers = [], $secure = null)

    $path = $this->session->pull('url.intended', $default);

    return $this->to($path, $status, $headers, $secure);

要实现重定向到用户来自您的页面,您可以

1 - 手动传递一些带有 url 的查询,例如 /login?redirect=like(not the url, just a mapping for /comments/like url)&amp;value=true(true is liked, false is unliked) 并手动处理。

2 - 从 url 获取并检查查询参数

3 - 使用 to() 方法来传递预期的 url,而不是使用预期的()。这是 to() 方法。 (看4看推荐方式)

/**
 * Create a new redirect response to the given path.
 *
 * @param  string  $path
 * @param  int     $status
 * @param  array   $headers
 * @param  bool    $secure
 * @return \Illuminate\Http\RedirectResponse
 */
public function to($path, $status = 302, $headers = [], $secure = null)

    return $this->createRedirect($this->generator->to($path, [], $secure), $status, $headers);

4 - 但是,我建议发送重定向 url(我的意思是映射 ex:like)作为对前端的响应,并让前端处理重定向。因为如果 API 仅由网站使用,则 API 重定向将起作用。假设如果您在移动应用程序中使用相同的 api,想知道 API 重定向将如何工作。重定向不是 API 的工作,除非它是用于 OAuth 身份验证之类的东西,它会指定一个重定向 url。

记得清理 url 参数以阻止 XSS 之类的东西。更好的 发送一些值并将其映射到 url。喜欢

[
   //like is mapping
   //comments/like is the actual url

   'like' => 'comments/like'
]

然后,从数组中获取映射 url 或使用前端映射。

【讨论】:

我听不懂。在我的 Javascript 中,我已经收到错误 401,我的问题是我希望用户在单击按钮时登录,并被重定向到他们单击按钮的原始页面。所以这就是我最初使用预期()方法的原因。 intended() 仅适用于会话。所以预期()不能用于api调用。您应该将 url 作为 url 查询传递,手动验证它并对其进行清理并将其手动发送到 to() 方法而不是预期的() 方法。第一个参数是预期的网址。 我明白了,如果我直接在 URL 中设置参数,然后在重定向中处理它,它应该可以工作。老实说,在 URL 中设置参数并不是最好的解决方案,但如果我没有找到其他任何东西,我会使用你的建议。谢谢! 我赞成你的建议,因为它非常丰富和详细,非常感谢!我仍然不相信将参数传递给 URL,因为它打开了恶意的大门,所以我会等待其他人是否有同样的问题并用更简单的解决方案解决;)再次感谢! 最后,由于使用 Ajax 意味着无状态,正如您正确建议的那样,没有后端解决方案。有趣的是,在使用 Laravel 之前,我认为自己是一名前端开发人员:P 再次感谢!【参考方案2】:

您可以在 RedirectIfAuthenticated.php 中进行更改,以区分 Ajax 调用和正常登录,如下所示:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    
        if (Auth::guard($guard)->check()) 

            if ($request->has('api_token')) 
                // do whatever you want

                return response()->json('some response'); 
            

                return redirect()->intended('/home');
        
        return $next($request);
    

更新:

另一种解决方案是从您的路由中删除 Auth 中间件。在APIController@set_like函数中手动登录用户,触发like并返回json响应。

【讨论】:

因为我通过这个调用身份验证:window.location = APP_URL + "/login" ;这不是 Ajax 调用。 删除 Auth 中间件不是解决方案,抱歉 :)【参考方案3】:

如果按钮不适合访客,则不应在页面上呈现它。 相反,您应该呈现一个登录链接,然后如果用户登录,您会将他重定向回他之前的位置。现在,用户可以看到并点击按钮。

【讨论】:

这更像是一个 UI/UX 决定,就我而言,我更喜欢向用户展示原始操作并鼓励他们加入。 因此,您可以在 JavaScript 端处理它。如果你收到一个 401 状态码,那么可能会显示一个弹出模式来登录,如果登录成功,那么你再试一次 ajax 请求。我认为在 laravel 方面没有其他方法可以处理这个问题。我不确定

以上是关于Laravel 5.6 上的 Ajax 身份验证重定向的主要内容,如果未能解决你的问题,请参考以下文章