在很多需求我们不希望别人知道用户在我们表中的 user_id ;
但是又想用数据库的自增 id 功能;
一般时候在取出用户后加密 user_id 加密即可;
但是总有那么几个不经意间就可能把我们的 user_id 暴露了;
比如说 laravel 的 passport ;

创建一个项目用于测试;

laravel new passport
Bash

安装 passport;

composer require laravel/passport
php artisan migrate
php artisan passport:install
Bash

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

 

将 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;
// ...
}
Diff

在 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();
}
}
Diff

将 config/auth.php 中的 driver 改为 passport;

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
], 'api' => [
- 'driver' => token',
+ 'driver' => 'passport',
'provider' => 'users',
],
],
Diff

创建测试用户

php artisan make:seeder UsersTableSeeder
Bash
<?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
php artisan db:seed --class=UsersTableSeeder
Bash

测试用户也有了;

 

获取下 access_token ;

 

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

 

可以看到 PAYLOAD 中的 sub 就是我们的未加密的用户id;
下面就是将 user_id 加密的过程了;

既然是加密id;
那还需要安装一个扩展包;
示例中我们使用laravel-hashids

composer require vinkla/hashids
php artisan vendor:publish --provider='Vinkla\Hashids\HashidsServiceProvider'
Bash

随便配置下;
/config/hashids.php

'main' => [
- 'salt' => 'your-salt-string',
- 'length' => 'your-length-integer',
+ 'salt' => 'alsd2987vnvczx&^$%Tpweqfhkjn',
+ 'length' => 20,
],
Diff

加密用户id;
这里主要用到了 laravel 留的一个钩子;
vendor/laravel/passport/src/Bridge/UserRepository.php

 

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

因为上面把 user_id 加密了;
而 oauth_access_tokens 表中的 user_id 是 int 类型;
所以我们需要在向 oauth_access_tokens 存储数据的时候自动解密 user_id ;

php artisan make:model OauthAccessToken
Bash
<?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));
}
}
}
PHP

覆盖 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);
}
}
Diff

再获取下 access_token ;

 

 

如我们所愿;
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();
}
}
PHP

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

'providers' => [
'users' => [
- 'driver' => 'extended',
+ 'model' => App\User::class,
]
],
Diff

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

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

 

至此通过password模式的加密和解密已经完成了;
然鹅坑爹的是如果使用authorization code模式;
因为id是从 code 获取的;
而不是从模型中获取的;
因此我们需要在获取 code 的步骤中加密 user_id ;
先来覆盖原本的 AuthorizationController 中的approve方法;

php artisan make:controller Oauth/AuthorizationController
Bash
<?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)
);
});
}
}
PHP

覆盖路由;
routes/web.php

/*
|--------------------------------------------------------------------------
| oauth
|--------------------------------------------------------------------------
*/
Route::prefix('oauth')->namespace('Oauth')->group(function () {
Route::post('authorize', 'ApproveAuthorizationController@approve')->name('passport.authorizations.approve');
});
PHP

此处有一个坑爹的地方;
passport 在向 oauth_auth_code 表中存储数据的时候使用了 setRawAttributes ;

 

以至于我们不能使用 setUserIdAttribute 来解密;
因此需要覆盖 OauthAuthCode 模型的 setRawAttributes 方法用于解密;

php artisan make:model OauthAuthCode
Bash

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

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

authorization code模式需要网页登录;

php artisan make:auth
Bash

访问 /login 登录;

 

生成client;

php artisan passport:client
Bash

 

手动组一个获取 code 的 url 并访问;
http://passport.test/oauth/authorize?client_id=3&redirect_uri=http://passport.test/auth/callback&response_type=code&scope=

 

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

 

检查 user_id 是否被加密;

 

检测 token 是否可用;

 

一切按着剧本走的没有什么问题;
我把示例代码上传到 github 上了;
如果自己按文章测试的过程中出现问题;
可用参考 https://github.com/baijunyao/laravel-passport-encrypt-user-id-demo

laravel passport加密jwt格式的access_token中的sub(user_id)字段的更多相关文章

  1. Laravel Passport认证-多表、多字段解决方案

    Laravel Passport认证-多表.多字段解决方案 2018年08月19日 09:31:01 醉卧码场君莫笑 阅读数:1632   1. 概述 API 通常使用令牌(token)进行认证并且在 ...

  2. Laravel Passport API 认证使用小结

    Laravel Passport API 认证使用小结 八月 4, 2017 发布在 Laravel 看到Laravel-China 社区常有人问 Laravel Passport 用于密码验证方式来 ...

  3. laravel Passport - 创建 REST API 用户认证以及Dingo/Api v2.0+Passport实现api认证

    第一部分: 安装passport 使⽤ Composer 依赖包管理器安装 Passport : composer require laravel/passport 接下来,将 Passport 的服 ...

  4. Jwt在Java项目中的简单实际应用

    1.什么是jwt 双方之间传递安全信息的简洁的.URL安全的表述性声明规范.JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信 ...

  5. laravel Passport - Dingo/Api v2.0+Passport 实现 api 认证

    第一部分: 安装passport 使⽤ Composer 依赖包管理器安装 Passport : composer require laravel/passport 接下来,将 Passport 的服 ...

  6. laravel passport client_credentials

    我是使用 Laravel 5.4 + Dingo Api + passport/jwt 两个验证方式 目前需要用到 passport 的 client_credentials 获取 token成功之后 ...

  7. APFS 宗卷 • APFS(加密)磁盘格式怎么去掉?Mac磁盘加密怎么解除?

    相信很多朋友都因为APFS 宗卷 • APFS(加密)磁盘格式而困扰,这种磁盘加密,导致很多破解版软件都不能安装.那么磁盘加密怎么解除?小编翻阅了一些教程,为您带来APFS 宗卷 • APFS(加密) ...

  8. (译)利用ASP.NET加密和解密Web.config中连接字符串

    介绍 这篇文章我将介绍如何利用ASP.NET来加密和解密Web.config中连接字符串 背景描述 在以前的博客中,我写了许多关于介绍 Asp.net, Gridview, SQL Server, A ...

  9. 利用ASP.NET加密和解密Web.config中连接字符串

    摘自:博客园 介绍 这篇文章我将介绍如何利用ASP.NET来加密和解密Web.config中连接字符串 背景描述 在以前的博客中,我写了许多关于介绍 Asp.net, Gridview, SQL Se ...

随机推荐

  1. How To Add Custom Build Steps and Commands To setup.py

    转自:https://jichu4n.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy/ A setup.py scrip ...

  2. bond模式

    1.mode=0(balance-rr)(平衡抡循环策略) 链路负载均衡,增加带宽,支持容错,一条链路故障会自动切换正常链路.交换机需要配置聚合口,思科叫port channel.特点:传输数据包顺序 ...

  3. 2018-2019-2 网络对抗技术 20165308 Exp4 恶意代码分析

    2018-2019-2 网络对抗技术 20165308 Exp4 恶意代码分析 实验过程 一.系统运行监控 (1)使用如计划任务,每隔一分钟记录自己的电脑有哪些程序在联网,连接的外部IP是哪里.运行一 ...

  4. 结合数据结构来看看Java的String类

    数据结构中定义字符串是由零个或多个字符组成的有限序列,有限,指出字符串的长度是一个有限的数值:所谓的序列,说明串的相邻字符之间具有前驱和后继的关系.字符串一般记为s="a1a2...an&q ...

  5. 18.13 Uboot分析与移植

    18.13.1 使用JLink烧写Nor Flash JLink只支持烧写NOR Flash,不支持烧写Nand Flash. 1.准备工作:JLink的USB口接到电脑上,JLink的JTAG口用排 ...

  6. python基础知识6---文件处理

    阅读目录 一 文件操作 二 打开文件的模式 三 操作文件的方法 四 文件内光标移动 五 文件的修改   一 文件操作 一 介绍 计算机系统分为:计算机硬件,操作系统,应用程序三部分. 我们用pytho ...

  7. 2018-2019-20175334实验二《Java面向对象程序设计》实验报告

    2018-2019-20175334实验二<Java面向对象程序设计>实验报告 一.实验内容及步骤 实验二面向对象程序设计-1 参考 http://www.cnblogs.com/roce ...

  8. [UE4]判断2个向量是否相等

    一.因为向量是3个浮点数,如果不使用误差的话,计算机里面的浮点数是有一定误差的. 二.如上图所示,如果2个向量的误差1厘米(UE4的单位是厘米),则表示2个向量相等.

  9. 数据库-Oracle的使用

    数据库的作用不言而喻,Oracle 作为一个主流的数据库,了解更是需要的 安装好oracle之后,登录初始账号,一般而言呢,我就新建一个用户, --创建新用户,用户名为JohnWu ,密码为 root ...

  10. Redis主从+读写分离中可以在从机读取到过期数据

    参考链接: https://mp.weixin.qq.com/s?__biz=MzIyNzUwMjM2MA==&mid=2247483696&idx=1&sn=c69e364b ...