Laravel 中的 HMAC 身份验证

2025-05-24

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
PREV
2020 年 11 款顶级 React 开发者工具 1. React Developers Tools 2. React Sight 3. Bit 4. React Extension Pack(适用于 VS Studio) 5. Storybook 6. React Styleguideist 7. Create React App 8. React Bootstrap 9. React-Proto 10. Why did you render 11. Proton Native 结论
NEXT
让我们谈谈青少年