发布于 2025-12-08 4 阅读
0

Laravel 中的 HMAC 身份验证

Laravel 中的 HMAC 身份验证

安全,安全,安全!

最近在开发一组 API webhook 时,我需要提供一些安全方案,以便客户端能够验证 webhook 请求是否由我正在开发的正确服务器发送。经过一番在线研究,我偶然发现了用于 API 请求的 HMAC 方案。简单解释一下之后,我将给出一个在 Laravel 中为 API 端点实现该方案的示例。

基于哈希的消息认证码 (HMAC) 是使用加密哈希函数、数据和密钥生成的代码。它用于验证请求来源和内容的真实性。客户端和服务器共享一个预先知道的密钥来生成 HMAC。

简单来说,服务器和客户端共享一个密钥,并且只有服务器和客户端知道。哈希函数会在每次发出请求时使用此密钥生成一个代码。然后,此代码会被添加到请求中并发送出去。接收应用程序收到请求后,会使用刚刚收到的请求信息和存储的密钥,运行与创建代码相同的流程。如果发送者的身份正确,请求中的代码和刚刚生成的代码应该匹配,接收者就可以继续处理请求了。是不是很简单?

这是一种简单而有效的身份验证方法,因为完整性检查不允许中间人攻击,除非他们拥有密钥。然而,攻击者可能会一遍又一遍地重复发送相同的请求,这可能会导致数据损坏或泄露一些敏感信息。这些被称为重放攻击,有很多方法可以应对这类攻击,但我不会讨论任何一种方法,因为我打算尽可能简化讨论。

现在简单解释一下我们的哈希函数。我们将使用的值是:

  • URL:小写的完整 URL,包括任何查询参数。
  • Verb:大写的请求的 HTTP 方法,例如 POST。
  • 内容 MD5:JSON 格式的请求主体的 MD5 哈希值

创建哈希码的步骤如下:

  1. 使用上面的值创建一个签名字符串。
  2. 使用字符串和我们的密钥生成哈希。
  3. 对哈希进行 Base64 编码并将其附加到请求标头

可以使用其他值,例如请求内容类型,但我尽量简单。每个 API 创建用于哈希计算的签名字符串的方案通常在 API 文档中描述。

设置

首先,启动一个新的 Laravel 应用程序并添加几个控制器来添加我们的 API 逻辑。

php artisan make:controller UsersController
php artisan make:controller ClientController
Enter fullscreen mode Exit fullscreen mode

在 routes/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']);
});
Enter fullscreen mode Exit fullscreen mode

我已经为路由分配了一个中间件auth.hmac,稍后我会再讨论这个问题。接下来,将示例公钥和密钥保存到文件中.env,并在配置文件中引用它们config/hmac.php。这里的公钥是 HMAC 的预期请求头密钥。

# .env
HMAC_PUBLIC_KEY="X-MEN-SIGNATURE"
HMAC_SECRET_KEY="Xavier's School for Gifted Youngsters"
Enter fullscreen mode Exit fullscreen mode
<!-- hmac.php -->
<?php
    return [
        'public' => 'X-MEN-SIGNATURE',
        'secret' => "Xavier's School for Gifted Youngsters"
    ];
Enter fullscreen mode Exit fullscreen mode

接下来,我们将在控制器中实现一些方法来创建、获取和编辑用户,以方便本教程的简单使用。用户模型随 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

把它弄乱

现在,我们已经为一个简单的 API 编写了有效的控制器逻辑。但是,由于缺少中间件,调用它会导致错误。让我们回过头来看一下。这个中间件将用于 HMAC 身份验证。在控制器处理请求之前,我们将确认它包含正确的 HMAC 头。

添加新的中间件:

php artisan make:middleware HmacAuth

并通过将其添加到$routeMiddleware进行注册App\Http\Kernel.php

protected $routeMiddleware = [
    ...
    'auth.hmac' => \App\Http\Middleware\HmacAuth::class,
    ...
];
Enter fullscreen mode Exit fullscreen mode

现在开始真正的工作。记住创建签名字符串的方案。让我们开始实现它。首先,我们检查所需的标头是否在请求中,如果不是,则中止请求。

$header = config('hmac.public');
$request_hash = $request->headers->get($header);
if (!$request_hash) {
    $message = 'Header `' . $header . '` missing.';
    abort('403', $message);
}
Enter fullscreen mode Exit fullscreen mode

如果匹配,我们将获取字符串,该字符串由 HTTP 方法、URL 和 JSON 格式的请求主体的 MD5 哈希值组成,中间使用换行符连接而成。然后,使用密钥对字符串运行 HMAC SHA-256 哈希算法。最终,该值使用 UTF8 进行 Base64 编码,并与 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

就这样。HMAC 签名验证是 API 请求和 webhook 身份验证最简单、最强大的方法之一。

文章来源:https://dev.to/delaneys/hmac-authentication-in-laravel-4je9