在 Laravel 微服务中解码 JWT 并创建经过身份验证的用户,与本地用户数据合并

use*_*915 1 php jwt laravel microservices

背景

我有一个微服务设置,流程是:

client > api gateway > auth server > api gateway > microservice
Run Code Online (Sandbox Code Playgroud)
  1. 客户拥有来自 Laravel 护照的“外部”JWT
  2. 客户端使用“外部”JWT 向 api 网关发送请求
  3. api 网关使用“外部”JWT 向身份验证服务器(Laravel 护照)发送请求
  4. 身份验证服务器验证用户是否仍处于活动状态,并向包含用户配置文件、组等的 api 网关返回一个新的“内部”JWT
  5. api 网关使用这个新的“内部”JWT 将请求转发到微服务
  6. (到目前为止一切都很好)
  7. 微服务使用身份验证服务器公钥验证“内部”JWT
  8. 微服务解码“内部”JWT 并从其中包含的配置文件创建用户对象
  9. 如果微服务有本地用户表(例如微服务特定用户数据),将本地数据与 JWT 数据合并

微服务认证

我创建了一个 JwtGuard,它可以解码 JWT 并使用 GenericUser 创建一个用户:

身份验证文件

'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],
],
Run Code Online (Sandbox Code Playgroud)

AuthServiceProvider.php

public function boot()
    {
        $this->registerPolicies();

        Auth::extend('jwt', function ($app) {
            return new JwtGuard($app['request']);
        });
    }
Run Code Online (Sandbox Code Playgroud)

JwtGuard.php

<?php
namespace App\Services\Auth;

use Illuminate\Auth\GenericUser;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;

use \Firebase\JWT\JWT;
use Illuminate\Http\Request;

class JwtGuard implements Guard {

    use GuardHelpers;

    /**
     * @var Request
     */
    private $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {
        if (!is_null($this->user)) {
            return $this->user;
        }

        if(!$jwt = $this->getJwt()) {
            return null;
        }

        return $this->decode($jwt);
    }

    /**
     * Validate a user's credentials.
     *
     * @param  array $credentials
     * @return bool
     */
    public function validate(array $credentials = [])
    {
        if(!$jwt = $this->getJwt()) {
            return false;
        }

        return !is_null($this->decode($jwt))?true:false;
    }

    /**
     * Decode JWT and return user
     *
     * @return mixed|null
     */
    private function decode($jwt)
    {
        $publicKey = file_get_contents(storage_path('oauth-public.key'));

        try {
            $res = JWT::decode($jwt, $publicKey, array('RS256'));
            return $this->user = new GenericUser(json_decode(json_encode($res->user), true));
        } catch (\Exception $e) {
            return null;
        }
    }

    private function hasAuthHeader()
    {
        return $this->request->header('Authorization')?true:false;
    }

    private function getJwt()
    {
        if(!$this->hasAuthHeader()){
            return null;
        }

        preg_match('/Bearer\s((.*)\.(.*)\.(.*))/', $this->request->header('Authorization'), $jwt);

        return $jwt[1]?$jwt[1]:null;
    }

}
Run Code Online (Sandbox Code Playgroud)

问题

这工作正常(ish),除了:

  • 我无法正确使用授权策略,因为 GenericUser 没有 can() 方法
  • 没有简单的方法可以与本地用户对象合并

到目前为止我所拥有的

我尝试了以下将本地用户数据与 JWT 配置文件合并:

private function decode($jwt)
    {
        $publicKey = file_get_contents(storage_path('oauth-public.key'));

        try {
            $res = JWT::decode($jwt, $publicKey, array('RS256'));
            $this->user = new GenericUser(json_decode(json_encode($res->user), true));
            $this->user->localUser = \App\User::where('user_id', $this->user->id)->first();
            return $this->user;
        } catch (\Exception $e) {
            return null;
        }
    }
Run Code Online (Sandbox Code Playgroud)

但这仍然使 GenericUser 没有 can() 函数。


请帮忙!

我不禁觉得有一种更好的(正确的?)方法可以使用“User”而不是“GenericUser”来实现这一点,这将允许 Laravel 中的所有身份验证/授权功能正常工作,并轻松合并数据。

use*_*915 5

我通过将 $jwt_user 添加到 User 构造以跳过“可填充”来解决它:

身份验证文件

'defaults' => [
    'guard' => 'api',
],
'guards' => [
    'api' => [
        'driver' => 'jwt',
    ],
],
Run Code Online (Sandbox Code Playgroud)

AuthServiceProvider.php

use App\User;
use \Firebase\JWT\JWT;

public function boot()
    {
        $this->registerPolicies();

        Auth::viaRequest('jwt', function ($request) {
            $publicKey = file_get_contents(storage_path('oauth-public.key'));

            if(!$hasAuthHeader = $request->header('Authorization')?true:false){
                return null;
            }

            preg_match('/Bearer\s((.*)\.(.*)\.(.*))/', $request->header('Authorization'), $jwt);

            try {
                $res                        = JWT::decode($jwt[1], $publicKey, array('RS256'));
                $jwt_user                   = json_decode(json_encode($res->user), true);
                $local_user                 = User::find($jwt_user['id']);
                $jwt_user['local_profile']  = $local_user?$local_user:[];
                $user                       = new User([], $jwt_user);
                return $user;
            } catch (\Exception $e) {
                return null;
            }
        });
    }
Run Code Online (Sandbox Code Playgroud)

用户名

public function __construct(array $attributes = array(), $jwt_user = array())
    {
        parent::__construct($attributes);

        foreach($jwt_user as $k=>$v){
            $this->$k = $v;
        }
    }
Run Code Online (Sandbox Code Playgroud)