php对用户id加密,laravel passport加密jwt格式的access_token中的sub(user_id)字段

在很多需求我们不希望别人知道用户在我们表中的 user_id ;

但是又想用数据库的自增 id 功能;

一般时候在取出用户后加密 user_id 加密即可;

但是总有那么几个不经意间就可能把我们的 user_id 暴露了;

比如说 laravel 的 passport ;

创建一个项目用于测试;laravel new passport

安装 passport;composer require laravel/passport

php artisan migrate

php artisan passport:install

现在我们有了用于测试的 Clint;

e1c6f1d3ea61b87630651d804d362de1.png

将 Laravel\Passport\HasApiTokens Trait 添加到 App\User 模型中;<?php

namespace App;

+ use Laravel\Passport\HasApiTokens;

use Illuminate\Notifications\Notifiable;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable

{

+ use HasApiTokens, Notifiable;

// ...

}

在 AuthServiceProvider 的 boot 方法中增加 Passport::routes() ;<?php

namespace App\Providers;

+ use Laravel\Passport\Passport;

use Illuminate\Support\Facades\Gate;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider

{

/**

* The policy mappings for the application.

*

* @var array

*/

protected $policies = [

'App\Model' => 'App\Policies\ModelPolicy',

];

/**

* 注册任何认证/授权服务。

*

* @return void

*/

public function boot()

{

$this->registerPolicies();

+ Passport::routes();

}

}

将 config/auth.php 中的 driver 改为 passport;'guards' => [

'web' => [

'driver' => 'session',

'provider' => 'users',

],

'api' => [

- 'driver' => token',

+ 'driver' => 'passport',

'provider' => 'users',

],

],

创建测试用户php artisan make:seeder UsersTableSeeder<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

factory(App\User::class, 5)->create();

}

}php artisan db:seed --class=UsersTableSeeder

测试用户也有了;

10c59703f1a92ed0b0fc8c7b31f833cb.png

获取下 access_token ;

04f1e37726d45403339251b84f3f3552.png

我们拿着 access_token 去 jwt.io 解开看下;

c1bceba929cd9f4a17b6d3c391712812.png

可以看到 PAYLOAD 中的 sub 就是我们的未加密的用户id;

下面就是将 user_id 加密的过程了;

既然是加密id;

那还需要安装一个扩展包;

示例中我们使用laravel-hashidscomposer require vinkla/hashids

php artisan vendor:publish --provider='Vinkla\Hashids\HashidsServiceProvider'

随便配置下;

/config/hashids.php'main' => [

- 'salt' => 'your-salt-string',

- 'length' => 'your-length-integer',

+ 'salt' => 'alsd2987vnvczx&^$%Tpweqfhkjn',

+ 'length' => 20,

],

加密用户id;

这里主要用到了 laravel 留的一个钩子;

vendor/laravel/passport/src/Bridge/UserRepository.php

3dadebc71ccb975401cee7b6afb3ff7b.png

在 getUserEntityByUserCredentials 中会判断 User 模型是否有 findForPassport 方法;

我们可以在此处加密;

app/User.php<?php

namespace App;

use Illuminate\Notifications\Notifiable;

use Illuminate\Contracts\Auth\MustVerifyEmail;

use Illuminate\Foundation\Auth\User as Authenticatable;

use Laravel\Passport\HasApiTokens;

use Hashids;

class User extends Authenticatable

{

use HasApiTokens, Notifiable;

/**

* The attributes that are mass assignable.

*

* @var array

*/

protected $fillable = [

'name', 'email', 'password',

];

/**

* The attributes that should be hidden for arrays.

*

* @var array

*/

protected $hidden = [

'password', 'remember_token',

];

/**

* 此处定义 getIdAttribute 是为了跳过模型把主键 id 转成数字的操作

*

* @see \Illuminate\Database\Eloquent\Concerns\HasAttributes::getAttributeValue()

*

* @param $value

* @return mixed

*/

public function getIdAttribute($value)

{

return $value;

}

/**

* 当获取用户的时候加密 user_id

*

* 下面这些方法是为了加密和解密 jwt 中的 user_id

* @see \App\Extensions\Illuminate\Auth\ExtendedUserProvider::retrieveById()

* @see \App\Models\User::findForPassport()

* @see \App\Models\OauthAccessToken::setUserIdAttribute()

*

* @param string $email

*

* @return User

*/

public function findForPassport($email): self

{

$user = $this->where('email', $email)->first();

$user->id = Hashids::encode($user->id);

return $user;

}

}

因为上面把 user_id 加密了;

而 oauth_access_tokens 表中的 user_id 是 int 类型;

所以我们需要在向 oauth_access_tokens 存储数据的时候自动解密 user_id ;php artisan make:model OauthAccessToken<?php

namespace App;

use Laravel\Passport\Token;

use Vinkla\Hashids\Facades\Hashids;

class OauthAccessToken extends Token

{

/**

* 当向 oauth_access_tokens 表中存储数据的时候解密 user_id

*

* 下面这些方法是为了加密和解密 jwt 中的 user_id

* @see \App\Extensions\Illuminate\Auth\ExtendedUserProvider::retrieveById()

* @see \App\Models\User::findForPassport()

* @see \App\Models\OauthAccessToken::setUserIdAttribute()

*

* @param int|string $value

*/

public function setUserIdAttribute($value): void

{

if (is_numeric($value)) {

$this->attributes['user_id'] = $value;

} else {

$this->attributes['user_id'] = current(Hashids::decode($value));

}

}

}

覆盖 passport 的 OauthAccessToken ;

app/Providers/AuthServiceProvider.php<?php

namespace App\Providers;

use App\OauthAccessToken;

use Illuminate\Support\Facades\Gate;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

use Laravel\Passport\Passport;

class AuthServiceProvider extends ServiceProvider

{

/**

* The policy mappings for the application.

*

* @var array

*/

protected $policies = [

'App\Model' => 'App\Policies\ModelPolicy',

];

/**

* Register any authentication / authorization services.

*

* @return void

*/

public function boot()

{

$this->registerPolicies();

Passport::routes();

+ Passport::useTokenModel(OauthAccessToken::class);

}

}

再获取下 access_token ;

6605f722d5fc668a8118c5aff9f10c57.png

fcc8c9bdffb7e97e7ae2dbfcc439ff92.png

如我们所愿;

user_id 已经被加密了;

接着还需要处理的是当我们拿着这个token去访问应用的时候能成功验证;

并且可以正确的获取到用户;

定义ExtendedUserProvider 用于覆盖 retrieveById 方法;

app/Extensions/Illuminate/Auth/ExtendedUserProvider.php<?php

namespace App\Extensions\Illuminate\Auth;

use App\User;

use Illuminate\Auth\AuthenticationException;

use Illuminate\Auth\EloquentUserProvider;

use Vinkla\Hashids\Facades\Hashids;

class ExtendedUserProvider extends EloquentUserProvider

{

/**

* 当获取用户的时候解密 user_id

*

* 下面这些方法是为了加密和解密 jwt 中的 user_id

* @see \App\Extensions\Illuminate\Auth\ExtendedUserProvider::retrieveById()

* @see \App\User::findForPassport()

* @see \App\OauthAccessToken::setUserIdAttribute()

*

* @param int|string $identifier

* @return User

* @throws AuthenticationException

*/

public function retrieveById($identifier)

{

$model = $this->createModel();

/**

* If Id is a string, then we need to decrypt $identifier.

*

* @see \App\Models\User::findForPassport()

*/

if (!is_numeric($identifier)) {

$identifier = current(Hashids::decode($identifier));

}

return $model->newQuery()

->where($model->getAuthIdentifierName(), $identifier)

->first();

}

}

修改配置项把 auth.providers.users.driver 改成

config/auth.php'providers' => [

'users' => [

- 'driver' => 'extended',

+ 'model' => App\User::class,

]

],

app/Providers/AuthServiceProvider.php<?php

namespace App\Providers;

use App\Extensions\Illuminate\Auth\ExtendedUserProvider;

use App\OauthAccessToken;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

use Laravel\Passport\Passport;

use Illuminate\Foundation\Application;

use Auth;

class AuthServiceProvider extends ServiceProvider

{

/**

* The policy mappings for the application.

*

* @var array

*/

protected $policies = [

'App\Model' => 'App\Policies\ModelPolicy',

];

/**

* Register any authentication / authorization services.

*

* @return void

*/

public function boot()

{

$this->registerPolicies();

Passport::routes();

Passport::useTokenModel(OauthAccessToken::class);

/**

* Register @see \App\Extensions\Illuminate\Auth\ExtendedUserProvider

*/

Auth::provider('extended', function ($app, $config) {

$model = $config['model'];

return new ExtendedUserProvider($app['hash'], $model);

});

}

}

使用 jwt 请求 /api/user 正确的通过的验证

f730508e3e328fd43b3649aa8465c518.png

至此通过password模式的加密和解密已经完成了;

然鹅坑爹的是如果使用authorization code模式;

因为id是从 code 获取的;

而不是从模型中获取的;

因此我们需要在获取 code 的步骤中加密 user_id ;

先来覆盖原本的 AuthorizationController 中的approve方法;php artisan make:controller Oauth/AuthorizationController<?php

namespace App\Http\Controllers\Oauth;

use Illuminate\Http\Request;

use Laravel\Passport\Http\Controllers\ApproveAuthorizationController as Controller;

use Zend\Diactoros\Response as Psr7Response;

use Hashids;

class ApproveAuthorizationController extends Controller

{

public function approve(Request $request)

{

return $this->withErrorHandling(function () use ($request) {

$authRequest = $this->getAuthRequestFromSession($request);

/**

* Encrypt user_id

*

* @see \App\OauthAuthCode::setRawAttributes()

*/

$user = $authRequest->getUser();

$user->setIdentifier(Hashids::encode($user->getIdentifier()));

return $this->convertResponse(

$this->server->completeAuthorizationRequest($authRequest, new Psr7Response)

);

});

}

}

覆盖路由;

routes/web.php/*

|--------------------------------------------------------------------------

| oauth

|--------------------------------------------------------------------------

*/

Route::prefix('oauth')->namespace('Oauth')->group(function () {

Route::post('authorize', 'ApproveAuthorizationController@approve')->name('passport.authorizations.approve');

});

此处有一个坑爹的地方;

passport 在向 oauth_auth_code 表中存储数据的时候使用了 setRawAttributes ;

c5263329cc3d4e525e9c208965250aea.png

以至于我们不能使用 setUserIdAttribute 来解密;

因此需要覆盖 OauthAuthCode 模型的 setRawAttributes 方法用于解密;php artisan make:model OauthAuthCode

app/OauthAuthCode.php<?php

namespace App;

use Hashids;

use Laravel\Passport\AuthCode;

class OauthAuthCode extends AuthCode

{

/**

* 因为 laravel passport 在 @see \Laravel\Passport\Bridge\AuthCodeRepository@persistNewAuthCode() 中使用的 setRawAttributes ; 而 setRawAttributes 不能触发 setUserIdAttribute ;所以不能使用 setUserIdAttribute 解密;需要 覆盖 setRawAttributes 方法;

*

* @param array $attributes

* @param bool $sync

*

* @return $this

*/

public function setRawAttributes($attributes, $sync = false)

{

/**

* Decrypt user_id

*

* Encrypt user_id in @see \App\Http\Controllers\Oauth\AuthorizationController::authorize()

*/

if (isset($attributes['user_id'])) {

$attributes['user_id'] = current(Hashids::decode($attributes['user_id']));

}

return parent::setRawAttributes($attributes, $sync);

}

}

app/Providers/AuthServiceProvider.php<?php

namespace App\Providers;

use App\Extensions\Illuminate\Auth\ExtendedUserProvider;

use App\OauthAccessToken;

+ use App\OauthAuthCode;

use Auth;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

use Laravel\Passport\Passport;

class AuthServiceProvider extends ServiceProvider

{

/**

* The policy mappings for the application.

*

* @var array

*/

protected $policies = [

'App\Model' => 'App\Policies\ModelPolicy',

];

/**

* Register any authentication / authorization services.

*

* @return void

*/

public function boot()

{

$this->registerPolicies();

Passport::routes();

Passport::useTokenModel(OauthAccessToken::class);

+ Passport::useAuthCodeModel(OauthAuthCode::class);

/**

* Register @see \App\Extensions\Illuminate\Auth\ExtendedUserProvider

*/

Auth::provider('extended', function ($app, $config) {

$model = $config['model'];

return new ExtendedUserProvider($app['hash'], $model);

});

}

}

authorization code模式需要网页登录;php artisan make:auth

访问 /login 登录;

538169f5f61ddb587dfd3d424e3460dd.png

生成client;php artisan passport:client

cb84d767b38477e6d7ef806e09b6419c.png

手动组一个获取 code 的 url 并访问;

http://passport.test/oauth/authorize?client_id=3&redirect_uri=http://passport.test/auth/callback&response_type=code&scope=

7c62f7d37a4d3be3ba874c0c94fad4ac.png

在回调地址 http://passport.test/auth/callback 页面地址栏获取 code ;

拿着 code 去换取 token ;

c9f858a0f5af8273aea36e5762b7a19a.png

检查 user_id 是否被加密;

383e68c5223185e864bdff74ccadd2d3.png

检测 token 是否可用;

c9e3444b429b31191be35e7972e9eceb.png

一切按着剧本走的没有什么问题;

我把示例代码上传到 github 上了;

如果自己按文章测试的过程中出现问题;

可用参考 https://github.com/baijunyao/laravel-passport-encrypt-user-id-demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值