You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何验证Keycloak Webhook请求?现有实现无效求指导

Keycloak Webhook签名验证失败的解决方法

问题描述

我正在实现一个接收Keycloak事件的Webhook端点,但不知道如何验证该请求。我发现请求包含“X-Keycloak-Signature”请求头,且已设置WEBHOOK_SECRET,推测需要通过请求与密钥生成签名并进行对比,于是编写了如下代码:

import os
import hashlib

from flask import abort, request


def validate_keycloak_signature(f):

    def wrapper(self, *args, **kwargs):
        secret = os.getenv("WEBHOOK_SECRET")
        method = request.method
        uri = request.url
        body = request.get_data(as_text=True)
        smub = secret + method + uri + body
        h = hashlib.sha256(smub.encode("utf-8")).hexdigest()
        signature = request.headers.get("X-Keycloak-Signature")
        if h != signature:
            return abort(403)
        return f(self, *args, **kwargs)
    
    return wrapper

我尝试的验证算法步骤如下:

  • 将Client secret、HTTP方法、URI、请求体(如有)拼接成字符串
  • 对该字符串生成SHA-256哈希值
  • 对比哈希值与签名,一致则验证通过

但该实现无法生效,请问有解决思路吗?

解决思路与修正方案

你的核心思路方向正确,但Keycloak的签名生成逻辑有几个细节未匹配,导致验证失败,具体修正点如下:

1. URI使用相对路径而非完整URL

Keycloak生成签名时用的是请求的相对路径(例如/webhook/keycloak),而不是包含协议、域名的完整URL。代码中需替换request.url为request.path。

2. 使用HMAC-SHA256算法而非纯哈希

Keycloak并非直接对拼接字符串做SHA256哈希,而是用WEBHOOK_SECRET作为密钥,通过HMAC-SHA256算法生成签名,不能把密钥直接拼入字符串。

3. 签名字符串的拼接规则

Keycloak要求的签名字符串格式是:HTTP方法\nURI\n请求体,各部分用换行符分隔,而非直接拼接。

4. 安全的签名对比方式

使用hmac.compare_digest()代替直接字符串相等判断,可避免时序攻击,提升安全性。

修正后的代码

import os
import hmac
import hashlib

from flask import abort, request


def validate_keycloak_signature(f):
    def wrapper(self, *args, **kwargs):
        secret = os.getenv("WEBHOOK_SECRET")
        if not secret:
            return abort(500)
            
        # 统一HTTP方法为大写
        method = request.method.upper()
        # 获取请求相对路径
        uri = request.path
        # 处理空请求体情况
        body = request.get_data(as_text=True) or ""
        
        # 按照Keycloak规则拼接签名字符串
        signature_payload = f"{method}\n{uri}\n{body}"
        # 计算HMAC-SHA256签名
        computed_signature = hmac.new(
            secret.encode("utf-8"),
            signature_payload.encode("utf-8"),
            hashlib.sha256
        ).hexdigest()
        
        incoming_signature = request.headers.get("X-Keycloak-Signature")
        # 安全对比签名
        if not hmac.compare_digest(computed_signature, incoming_signature):
            return abort(403)
        return f(self, *args, **kwargs)
    
    return wrapper

额外检查项

  • 确认WEBHOOK_SECRET与Keycloak后台配置的Webhook Secret完全一致,注意大小写、特殊字符及空格。
  • 确保请求体读取的编码为UTF-8,与Keycloak发送的编码匹配。
  • 验证HTTP方法是否与Keycloak发送的一致(通常为POST)。

内容的提问来源于stack exchange,提问作者Alexander Shpindler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 17:35:22