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

时间:2018-06-21 14:42:41

标签: php laravel jwt microservices

背景

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

client > api gateway > auth server > api gateway > microservice
  1. 客户有来自Laravel护照的“外部” JWT
  2. 客户端使用“外部” JWT将请求发送到api网关
  3. api网关使用“外部” JWT将请求发送到身份验证服务器(Laravel护照)
  4. 身份验证服务器验证用户仍处于活动状态,并将新的“内部” JWT返回到包含用户个人资料,组等的api网关
  5. api网关使用此新的“内部” JWT将请求转发到微服务
  6. (目前为止一切正常)
  7. 微服务使用auth服务器公钥验证“内部” JWT
  8. 微服务对“内部” JWT进行解码,并根据其中包含的配置文件创建用户对象
  9. 如果微服务具有本地用户表(例如用于微服务特定的用户数据),则将本地数据与JWT数据合并

微服务身份验证

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

auth.php

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

        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],
],

AuthServiceProvider.php

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

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

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

}

问题

这可以正常工作,除了:

  • 我无法正确使用授权策略,因为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;
        }
    }

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


帮助...请!

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

2 个答案:

答案 0 :(得分:0)

我通过在用户构造中添加$ jwt_user来跳过“可填充”来解决此问题:

auth.php

'defaults' => [
    'guard' => 'api',
],
'guards' => [
    'api' => [
        'driver' => 'jwt',
    ],
],

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

User.php

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

        foreach($jwt_user as $k=>$v){
            $this->$k = $v;
        }
    }

答案 1 :(得分:0)

实现这一目标的简单方法是:

use Firebase\JWT\JWT;
use Laravel\Passport\Token;

$jwt = 'eyJ0...';
$publicKey = file_get_contents(storage_path('oauth-public.key'));
$res = JWT::decode($jwtToken, $publicKey, ['RS256']);
$user = Token::findOrFail($res->jti)->user;