Laravel JWT 多页面结构

Posted

技术标签:

【中文标题】Laravel JWT 多页面结构【英文标题】:Laravel JWT Multi-Page Structure 【发布时间】:2018-09-01 02:55:00 【问题描述】:

据我所知,基于 JWT 的授权系统通常是为 SPA 保留的(你知道,一个视图,一个 React/Angular/Vue 应用程序,一个膨胀的 app.js 文件),但是我正在尝试利用JWT 的魔力与稍微独立的结构化应用程序。

结构

我试图提供两个独立的blade.php 视图,每个视图都作为自己的@ 987654327@: 一个用于应用程序的外部(pre-auth),另一个用于应用程序的内部(post-auth)。

应用的当前状态

为了支持我的应用程序的身份验证系统,我使用了Tymon's jwt-auth lib(顺便说一句漂​​亮的 lib)并将前面的所有内容与(如前所述)Vue/Vuex 绑定在一起。一切都按预期工作,在我的 RegisterLogin 组件中,我可以访问我的 api,获取 JWT 作为响应,将其存储在本地,然后将所述令牌附加到我的 Axios 标头中,允许所有后续请求包含此令牌。

两难境地

现在我正处于十字路口。我要提供的身份验证后路由/视图受自定义 JWT 中间件保护,如果未提供有效令牌,该中间件会重定向:

Route::get('/home', 'Auth\HomeController@home')->middleware('jwt');

中间件

class JWT

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    
        JWTAuth::parseToken()->authenticate();

        return $next($request);
    

我的 pre-auth 视图及其所有路由都受到 Laravel 的本地访客 RedirectIfAuthenticated 中间件的保护,现在由 JWT 保护:

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('/home');
        

        return $next($request);
    

问题

所以这归结为以下问题:

1) 在前端成功注册/登录并生成 JWT 并存储在本地和 Axios 标头中后,如何使用此有效令牌重定向到我的 post-auth 路由?

2) 我如何确保 Valid JWT 持续存在并在访客路由被命中以成功重定向回我的 post-auth 路由时存在?

如果可行,我希望将所有重定向和持久性检查保留在后端

【问题讨论】:

这两个应用程序是在同一个域还是在不同域? 同域,默认 Laravel 安装/项目,前端带有 Vue @Lassi Uosukaimen 所以问题是您在重定向到另一个页面后没有可用的令牌?或者确实只是刷新同一个页面? 确切地说,令牌是在后端生成并发送到前端存储在本地(在浏览器存储中)并添加到 axios 标头中,因此每个后续的 ajax 请求都使用令牌@tobbr 发送跨度> 将令牌存储在 cookie 中,而不是本地存储中。安全等。设置 JWT 的超时时间以匹配 Laravel 会话的超时时间。检查每个请求的中间件中的令牌是否过期,如果过期,将它们重定向到登录页面。在每个后续 axios 请求中从 cookie 中发送不记名令牌。 【参考方案1】:

成功登录后,您将获得令牌,假设其名为$jwt_token

您可以在获得授权后重定向到您正在保护的页面并在响应中设置 cookie:

return redirect('/home')->cookie(
    'access_token', //name
    $jwt_token,  //value
    config('session.lifetime'), //expiration in minutes (matches laravel)
    config('app.url'),  // your app url
    true // HttpsOnly
);

从这里,Axios 可以通过解析文档上的 cookie 并检索 access_token 来访问 cookie

let token = document.cookie.split(';') // get all your cookies
    .find(cookie => cookie.includes('access_token')) // take only the one that matches our access_token name
    .split('=')[1] // get just the value after =

// terrible code example above for you

现在您可以在 Axios 请求中使用它,方法是将其作为值添加到 Authorization 标头中的 Bearer

Authorization: `Bearer $token`

您的 JWT 中间件已经利用了 authenticate 方法,因此它应该按原样为您处理 expiry

JWTAuth::parseToken()->authenticate();

在后台,这将尝试根据config/jwt.php 文件中设置的当前 TTL 验证令牌的到期时间。鉴于您的工作流程,如果令牌过期,我也会blacklist 令牌。您可以添加一个Event Listener,用于侦听过期令牌并通过侦听Event::listen('tymon.jwt.expired'); 将其列入黑名单。

请原谅任何语法错误、格式问题或拼写错误,我正在电话中,稍后将进行编辑以解决这些问题。

【讨论】:

是的,您登录后的重定向方法与我的想法一致,但是,JWT 中间件要求将令牌与请求一起发送到其保护的任何路由,所以除非我可以在标头中设置令牌或以某种方式发送令牌时重定向到/home,永远无法到达/home,只是重定向回来 我希望每个请求都应该有令牌,甚至来自不同的控制器,我该怎么做?【参考方案2】:

因此,您可以通过多种方式确保 JWT 令牌在 Axios 或任何前端都可用。

最常见的方式是将令牌存储在cookie 或浏览器的Web 存储中(localStorage / sessionStorage)

localStorage 和 sessionStorage 的区别在于 localStorage 中存储的数据通过浏览器会话持久化,sessionStorage 在页面会话结束时被清除。

普遍的共识是 cookie 稍微安全一些,因为它们的攻击向量更小,尽管这两种方法都不是完全安全的。如果您想更深入地了解,可以从阅读this 文章开始。

为了更具体地了解您的问题,首先您要使用上述方法之一设置令牌存储,推荐的方法是 cookie,您可以找到如何使用纯 javascript here 进行设置的示例。

现在您在每个页面上都有令牌,您可以按照您喜欢的方式重定向用户。尽管我建议您不要使用自己的中间件进行 JWT 身份验证,而可以使用 JWT 库提供的中间件:jwt.auth

如果令牌有问题,此中间件将自动以错误代码响应,如果有,它将返回以下 HTTP 响应之一:

token_not_provided token_expired token_invalid user_not_found

如果返回这些响应之一(或者如果请求状态代码为 400),您可以简单地使用前端将用户重定向回您的预授权路由。

登录时,将令牌保存到 cookie 后,使用前端重定向到 post-auth 路由。

我知道你说过你想在后端保留重定向逻辑,但是当你登录时调用 API 时,这并没有真正意义,你不能同时返回令牌和仅从后端同时导致重定向。

更新

一个非常简单的示例,说明如何仅使用警卫进行身份验证并仍然获得 API 令牌。借鉴@Ohgodwhy 的重定向示例,您可以将以下内容放入您的RedirectIfAuthenticated 中间件中。

public function handle($request, Closure $next, $guard = null)
    if (Auth::guard($guard)->check()) 
        if ((\Cookie::get('access_token') == null)) 
            $cookie = \Cookie::make(
                'access_token',
                \JWTAuth::fromUser(Auth::user()),
                config('session.lifetime'),
                null,
                $request->refeerer,
                false, // to make the cookie available in javascript
                false // to make the cookie available in javascript
            );

            return redirect('/home')->cookie($cookie);
         else 
            return redirect('/home');
        
    

    return $next($request);

只需确保您在app/Http/Controllers/Auth/LoginController.php 中的$redirectTo 设置为实现RedirectIfAuthenticated 中间件的路径。

【讨论】:

我的意思是我不能吗?在我后端的registerlogin 方法中,在成功进行身份验证和生成令牌后,我不能只调用重定向到我的post-auth 路由,添加一个值为令牌的自定义标头吗?跨度> 据我所知,不是在响应中返回 cookie,而是您可以使用@Ohgodwhy 描述的方法从后端设置 cookie,然后您不需要返回它. 是的,但这仍然不能回答重定向到我的身份验证后路由的问题,该令牌以标头或表单数据的形式存在。即使我在前端进行重定向,我将如何(在 JS 中)使用自定义标头发出请求? 如果保护检查失败,为什么不直接修改 RedirectIfAuthenticated 中间件以重定向到您的预授权路由? 不是真的,因为您可以使用$token = JWTAuth::fromUser(Auth::user()); 为已通过警卫身份验证的用户获取令牌。这就是 JWT 在后台所做的。但是如果你不想那样做,那么我想你必须在你的 jwt 中间件中做 JWTAuth::setToken($request->cookie('access_token'))->authenticate(); 而不是从请求中解析它。【参考方案3】:

所以这是我最终实现的逻辑:

在我的LoginController.php 登录函数中,在成功验证并生成 JWT 后,我返回一个带有 Json 和一个新 cookie 的响应,两者都通过了新的token

public function login(Request $request)

    $creds = $request->only(['email', 'password']);

    if (!$token = auth()->attempt($creds)) 
        return response()->json([
            'errors' => [
                'root' => 'Incorrect Credentials. Try again'
            ]
        ], 401);
    

    return $this->respondWithToken($token);


protected function respondWithToken($token)

    return response()->json([
        'meta' => [
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth()->factory()->getTTL() * 60
        ]
    ], 200)
    ->withCookie(cookie('access_token', $token, auth()->factory()->getTTL()));

在我的客人 RedirectIfAuthenticated 中间件中检查 cookie,如果存在,setToken 反过来将 Guard 设置为 Authenticated,并且如果令牌可用且有效,将始终重定向到 /home

public function handle($request, Closure $next, $guard = null)

    if ($request->hasCookie('access_token')) 
        Auth::setToken($request->cookie('access_token'));
    

    if (Auth::guard($guard)->check()) 
        return redirect('/home');
    

    return $next($request);

在我的post-auth Routes 中间件中,我还有setToken,如果它有效且存在,将允许访问,否则将抛出一系列JWT 错误,这些错误只是重定向到预授权视图:

public function handle($request, Closure $next)

    JWTAuth::setToken($request->cookie('access_token'))->authenticate();

    return $next($request);

最后,我决定在前端处理重定向,因为我使用的是基于承诺的 Axios,并且可以确保在重定向到授权后视图之前设置 cookie,因此不会发生任何有趣的事情!干杯!希望这可以帮助任何人寻求 Multi-Page SPA 魔法!

【讨论】:

以上是关于Laravel JWT 多页面结构的主要内容,如果未能解决你的问题,请参考以下文章

多用户模型 Laravel JWT Auth

Laravel6.0 使用 Jwt-auth 实现多用户接口认证

(ReactJS-Laravel) JWT Authentication Header (获取用户信息)

验证授权和未授权用户 (JWT ReactJS-Laravel)

JWT 身份验证 Laravel

laravel / angularjs JWT 令牌刷新