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

4
我有一个“喜欢”按钮,它触发了一个Ajax Post请求。此路由受auth:api中间件保护:
我的项目/路由/api.php
Route::group(['middleware' => ['auth:api']], function () {
    Route::post('/v1/like', 'APIController@set_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" ;
        }
    });
};

这里的问题在于intended()函数,在Ajax调用中未存储正确的会话变量,我无法弄清如何正确设置它。 显然我错过了一些明显的东西,有人能帮助吗? 谢谢!
编辑: 我想要实现以下目标: 1. GUEST在//mysite/blabla中 2. 点击“喜欢”按钮 3. 被重定向到登录页面 4. 登录(或注册) 5. 被重定向到//mysite/blabla,并已经触发了“喜欢”按钮。

没有API会话,因为它是无状态的。 - aimme
是的,但一定有方法来处理这个问题。 - Marco Cazzaro
只需从API发送401响应,然后您需要从前端处理它。 - aimme
don't redirect from api. - aimme
3个回答

3
发生的情况是,在API中会话未被管理,换句话说,它是无状态的。因此,Laravel框架没有为API请求实现会话中间件。虽然您可以手动添加,但不建议使用。因此,如果API不使用会话并使用重定向,则前端不知道它,因为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');
}

这将发送401未经身份验证的消息给用户,如果请求是json(api请求),否则(如果是web请求)将重定向到登录页面。
请查看下面的render方法或从源代码中查看以了解发生了什么。当抛出未经授权的异常时,我们告诉检查请求类型,如果来自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()方法仅适用于Web路由,因为它使用会话来提取预期的路由或手动传递的值。这里是intended()方法。

/**
 * 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- 手动传递一些查询参数,例如/login?redirect=like(不是url,只是/comments/like url的映射)&value=true(true表示喜欢,false表示不喜欢),并手动处理它。
2- 获取和检查来自url的查询参数。
3- 使用to()方法传递预期的url,而不是使用intended()方法。这是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(即映射如)作为响应发送到前端,并让前端处理重定向。由于API重定向仅适用于网站,因此如果您将此API用于移动应用程序,API重定向将无法正常工作。除非像OAuth认证这样的事物需要指定重定向URL,否则API不负责重定向。

Remember to sanitize the url params to block XSS like stuff. Better Send some values and map it to the urls. Like

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

   'like' => 'comments/like'
]

Then, get the mapping url from array or use frontend mappings.


我无法理解。在我的Javascript中,我已经得到了401错误,我的问题是,如果用户点击按钮,我希望他们登录,并被重定向回他们最初点击按钮的页面。因此,我最初使用了intended()方法。 - Marco Cazzaro
我明白你的意思,如果我直接在URL中设置参数,然后在重定向中处理它,应该可以解决问题。老实说,在URL中设置参数并不是最好的解决方案,但如果我找不到其他方法,我会使用你的建议。谢谢! - Marco Cazzaro
1
我点赞了你的建议,因为它非常丰富和详细,非常感谢!但我仍然不确定是否将参数传递到URL,因为这会打开恶意攻击的大门,所以我会等待看看是否有其他人遇到了同样的问题,并用更简单的解决方案解决。再次感谢! - Marco Cazzaro
是的,我也是。现在看到编辑了。我不建议从api重定向。请参见第4点。在前端使用映射,将响应发送到前端,让前端处理重定向。 - aimme
1
最后,由于使用Ajax意味着无状态,正如您正确建议的那样,没有后端解决方案。有趣的是,在使用Laravel之前,我认为自己是一名前端开发人员: P 再次感谢! - Marco Cazzaro
显示剩余3条评论

1

You can make changes in your RedirectIfAuthenticated.php to distinguish between Ajax call & normal login like this:

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);
    }
}

Update:

Another solution is to remove Auth middleware from your route. In APIController@set_like function manually login user, trigger like & return json response.


由于我是通过以下方式调用身份验证的: window.location = APP_URL + "/login" ;这不是一个Ajax调用。 - Marco Cazzaro
移除身份验证中间件并不是一个解决方案,抱歉 :) - Marco Cazzaro

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

这更多是一个UI / UX决策,在我的情况下,我更喜欢向用户展示原始操作并鼓励他们参与。 - Marco Cazzaro
所以,你可以在JavaScript端处理它。 如果收到401状态码,则可以显示一个弹出模态框进行登录,如果登录成功,则再次尝试ajax请求。我认为在Laravel端没有其他处理方式。我不确定。 - Ruman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接