最近在一组API Webhooks工作时,我需要提供一些安全方案,使客户可以验证我正在处理的正确服务器发送Webhook请求。经过一番在线研究,我遇到了HMAC计划的API请求计划。经过一点解释,我将举一个为Laravel中的API端点实施的示例。
基于哈希的消息身份验证代码(HMAC)是使用加密哈希功能,数据和秘密获得的代码。它用于验证请求源和内容的真实性。客户端和服务器都共享用于生成HMAC的预先秘密密钥。
用更简单的话来说,只有服务器和客户端共享并知道一个秘密密钥。 Hash函数使用此键在提出请求时生成代码。然后将此代码添加到请求中并发送。当接收应用程序获取请求时,它运行与创建代码相同的过程,使用刚收到的请求和已存储的秘密密钥的详细信息。如果发件人确实是他们本应成为的人,则请求中的代码和刚刚生成的代码都应匹配,并且接收器可以继续处理请求。很干净,对
这是一种简单而有效的身份验证方法,因为完整性检查不允许中间攻击,除非它们拥有秘密钥匙。但是,一个坏人可以一遍又一遍地重新提出相同的请求,这可能会损害数据或揭示一些敏感信息。这些被称为重播攻击,有多种处理方法,但是我不会谈论任何问题,以使其尽可能简单。
现在可以快速解释我们的哈希功能。我们将使用的值是:
- url :小写的完整URL,包括任何查询参数。
- 动词:大写速度中请求的HTTP方法,例如发帖。
- 内容MD5 :JSON中请求正文的MD5哈希
我们将采取的步骤创建哈希代码:
- 使用上面的值创建一个符号字符串。
- 使用字符串和我们的秘密密钥生成哈希。
- base64编码哈希并将其连接到请求标题
可以使用其他值,例如请求内容类型,但我会保持简单。通常在API文档中描述了用于创建用于哈希的签名字符串的每个API的方案。
配置
要启动,请旋转一个新的Laravel应用程序并添加几个控制器以添加我们的API逻辑。
php artisan make:controller UsersController
php artisan make:controller ClientController
在路由/api.php中为端点添加一些路由
Route::prefix('/')->middleware('auth.hmac')->group(static function () {
Route::get('/users', [App\Http\Controllers\UsersController::class, 'getAll']);
Route::get('/users/{id}', [App\Http\Controllers\UsersController::class, 'getOne']);
Route::post('/users', [App\Http\Controllers\UsersController::class, 'create']);
Route::put('/users/{id}', [App\Http\Controllers\UsersController::class, 'update']);
});
我将中间件auth.hmac
分配给路线,稍后再返回。接下来,将示例保存在.env
文件中,然后将它们引用在配置文件config/hmac.php
中。这里的公共密钥是HMAC的预期请求标头密钥。
# .env
HMAC_PUBLIC_KEY="X-MEN-SIGNATURE"
HMAC_SECRET_KEY="Xavier's School for Gifted Youngsters"
<!-- hmac.php -->
<?php
return [
'public' => 'X-MEN-SIGNATURE',
'secret' => "Xavier's School for Gifted Youngsters"
];
接下来,控制器中的一些方法可以为本教程的简单上下文创建,获取和编辑用户。用户模型随附Laravel安装;我们不需要更改任何东西。
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class UsersController extends Controller
{
public function getAll(Request $request)
{
$users = User::all();
return response()->json($users);
}
public function getOne(Request $request, $id)
{
$user = User::findOrFail($id);
return response()->json($user);
}
public function create(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email|unique:users',
'password' => 'required|min:6'
]);
if ($validator->fails()) {
return response()->json($validator->errors()->first(), 400);
}
extract($request->all());
$password = Hash::make($password);
$user = User::create(compact('name', 'email', 'password'));
return response()->json($user);
}
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$data = [
'name' => $request->name ?? $user->name,
'email' => $request->email ?? $user->email,
'password' => Hash::make($request->name) ?? $user->password
];
$user->update($data);
return response()->json($user);
}
}
哈希
现在,我们有一个简单API的工作控制器逻辑。但是,呼叫它会导致由于缺少中间件而导致错误。让我们回到这一点。此中间件将成为HMAC身份验证的位置。在控制器处理请求之前,我们将确认它包含正确的HMAC标头。
添加一个新的中间件:
php artisan make:middleware HmacAuth
并通过将其添加到$routeMiddleware
中的$routeMiddleware
进行注册。
protected $routeMiddleware = [
...
'auth.hmac' => \App\Http\Middleware\HmacAuth::class,
...
];
现在,对于真正的工作。记住用于创建签名字符串的方案。让我们继续实施它。首先,我们检查我们是否需要的标题是否是根据请求,如果它不是。
$header = config('hmac.public');
$request_hash = $request->headers->get($header);
if (!$request_hash) {
$message = 'Header `' . $header . '` missing.';
abort('403', $message);
}
如果是的,我们会得到http方法,url和json中请求主体的MD5哈希的串联,即使用newlines的串联。然后,我们使用秘密键,然后在字符串上运行HMAC SHA-256哈希算法。该值最终使用UTF8编码,并将其与我们的HMAC标头中的值进行了比较。如果它们匹配,我们可以继续处理该请求,否则我们会中止。
这是最终代码应该是什么样的:
class HmacAuth
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$header = config('hmac.public');
$request_hash = $request->headers->get($header);
if (!$request_hash) {
$message = 'Header `' . $header . '` missing.';
abort('403', $message);
}
$body = $request->all();
$url = config('hmac.webhook');
$verb = $request->method();
$md5 = md5(json_encode($body));
$string = $verb . PHP_EOL . $url . PHP_EOL . $md5;
$hash = hash_hmac('SHA256', $string, config('hmac.secret'));
$base64_hash = base64_encode($hash);
if ($base64_hash !== $request_hash) {
$message = 'Invalid `' . $header . '` Header';
abort('403', $message);
}
return $next($request);
}
}
这就是全部。 HMAC签名验证是API请求和Webhook身份验证的最简单,最强大的方法之一。