update
This commit is contained in:
@@ -9,16 +9,28 @@ class ClientAccessTokenCheck
|
||||
{
|
||||
/**
|
||||
* Verify the `x-access-token` header contains an AES-256-CBC ciphertext
|
||||
* (base64(iv . ciphertext)) of the shared secret, encrypted with the
|
||||
* openssl key configured in services.client_api.key. Blocks callers
|
||||
* that don't hold the shared key (external scraping/direct API calls).
|
||||
* (base64(iv . ciphertext)) of JSON {"s": <shared secret>, "t": <unix ts>, "ip"?: <client ip>},
|
||||
* encrypted with the openssl key configured in services.client_api.key. Blocks callers
|
||||
* that don't hold the shared key (external scraping/direct API calls);
|
||||
* the timestamp caps replay of a captured header to MAX_SKEW_SECONDS.
|
||||
*
|
||||
* portal-client gọi API từ Next server nên REMOTE_ADDR luôn là IP của Next. IP thật của khách
|
||||
* nằm trong payload đã mã hoá (không giả mạo được như X-Forwarded-For) → gán lại REMOTE_ADDR
|
||||
* để throttle/log dùng đúng IP. Middleware này được ưu tiên chạy trước ThrottleRequests (Kernel).
|
||||
*/
|
||||
private const MAX_SKEW_SECONDS = 300;
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$token = $request->header('x-access-token', '');
|
||||
$key = config('services.client_api.key');
|
||||
$secret = config('services.client_api.secret');
|
||||
$payload = $key && $secret ? $this->decode($request->header('x-access-token', ''), $key) : null;
|
||||
|
||||
if (!$key || !$this->isValid($token, $key)) {
|
||||
if (!$payload
|
||||
|| !is_string($payload['s'] ?? null)
|
||||
|| !hash_equals($secret, $payload['s'])
|
||||
|| !is_int($payload['t'] ?? null)
|
||||
|| abs(time() - $payload['t']) > self::MAX_SKEW_SECONDS) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Invalid access token',
|
||||
@@ -26,25 +38,23 @@ class ClientAccessTokenCheck
|
||||
], 403);
|
||||
}
|
||||
|
||||
if (filter_var($payload['ip'] ?? null, FILTER_VALIDATE_IP)) {
|
||||
$request->server->set('REMOTE_ADDR', $payload['ip']);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function isValid(string $token, string $key): bool
|
||||
private function decode(string $token, string $key): ?array
|
||||
{
|
||||
$raw = base64_decode($token, true);
|
||||
if ($raw === false || strlen($raw) <= 16) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
$iv = substr($raw, 0, 16);
|
||||
$ciphertext = substr($raw, 16);
|
||||
$plaintext = openssl_decrypt(substr($raw, 16), 'aes-256-cbc', hex2bin($key), OPENSSL_RAW_DATA, substr($raw, 0, 16));
|
||||
$payload = $plaintext === false ? null : json_decode($plaintext, true);
|
||||
|
||||
$plaintext = openssl_decrypt($ciphertext, 'aes-256-cbc', hex2bin($key), OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($plaintext === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_equals(config('services.client_api.secret'), $plaintext);
|
||||
return is_array($payload) ? $payload : null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user