如何让 OpenID Connect 使用客户端证书而不是机密 (PHP) 工作?

How to get OpenID Connect to work using client certificate rather than secret (PHP)?

提问人:Nick 提问时间:11/7/2023 更新时间:11/13/2023 访问量:107

问:

我是 OIDC 的新手,正在部署 SSO 应用程序。使用具有以下代码的客户端密码时,一切正常,但我公司的 Azure AD/Entra 配置要求在使用移动设备时使用客户端证书(而不是密码)。

我已使用 openssl 生成了一个密钥对,并将公钥上传到 Azure 门户,确认指纹正确无误。但是,我无法弄清楚如何将私钥输入到我的代码中。有人可以帮忙吗?

require_once 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;
$oidc = new OpenIDConnectClient('*url*', '*clientid*', null);
$oidc->setTokenEndpointAuthMethodsSupported(['private_key_jwt']);
$oidc->authenticate();
print_r($oidc->requestUserInfo());
PHP azure-active-directory openid-连接

评论

0赞 Olivier 11/12/2023
我不是专家,但客户端证书不应该安装在移动设备上吗?
0赞 Nick 11/13/2023
@Olivier我相信公钥已添加到 Azure 门户,私钥已嵌入到 PHP 代码中。因此,这可检查 OIDC 客户端是否有权使用 Azure AD 终结点。如果私钥应该从客户端传输到移动设备(这似乎是安全风险),那么关于实现这一点的建议也值得赞赏。目标是能够使用客户端证书评分器而不是客户端密码进行身份验证。
1赞 Olivier 11/13/2023
“目标是能够授权......”授权谁?运行 PHP 的服务器?还是通过其移动设备访问 Web 应用程序的最终用户?
0赞 Nick 11/13/2023
感谢您的反馈。是的,最终用户需要通过 Azure 进行身份验证,并且需要将其传达给 PHP 服务器(OIDC 客户端)。

答:

2赞 Nick 11/13/2023 #1

经过多次搜索和结合各种资源,我想我找到了答案。在这里发帖给可能有同样问题的其他人。

要点:

  1. 启用“private_key_jwt”作为支持的身份验证方法,使用$oidc->setTokenEndpointAuthMethodsSupported()
  2. 编写一个自定义函数,该函数使用由私钥签名的客户端信息创建标准 JWT。将其绑定到 $oidc 使用$oidc->setPrivateKeyJwtGenerator()
  3. Azure/OpenSSL 生成的公共证书指纹是一个十六进制字符串(如 AB12C2D5FF...)- 这需要转换为二进制文件,然后进行 base64 编码,作为 'x5t' 参数添加到 JWT 标头中:$headers['x5t'] = \base64_encode(\hex2bin('*public_key_thumbprint*'))

PHP OIDC 客户端代码:

require_once 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;

$clientId = '*clientId*';
$oidc = new OpenIDConnectClient('*url*', $clientId, null);
$oidc->setTokenEndpointAuthMethodsSupported(['private_key_jwt']);
$oidc->setPrivateKeyJwtGenerator(function (string $token_endpoint) {
    global $clientId;
    $key = \file_get_contents('*private_key_file*');
    $headers = [
        'typ' => 'JWT',
        'alg' => 'RS256',
        'x5t' => \base64_encode(\hex2bin('*public_cert_thumbprint*')),
    ];
    $payload = [
        'iss' => $clientId,
        'sub' => $clientId,
        'aud' => $token_endpoint,
        'jti' => \base64_encode(\random_bytes(16)),
        'exp' => \time() + 300,
    ];
    openssl_sign(
        base64UrlEncode(json_encode($headers)) . "." . base64UrlEncode(json_encode($payload)),
        $signature,
        $key,
        "sha256WithRSAEncryption"
    );
    $jwt = base64UrlEncode(json_encode($headers)) . "." . base64UrlEncode(json_encode($payload)) . "." . base64UrlEncode($signature);

    return $jwt;
});
$oidc->authenticate();
print_r($oidc->requestUserInfo());

function base64UrlEncode(string $text): string
{
    return rtrim(strtr(base64_encode($text), '+/', '-_'), '=');
}
`

评论

0赞 Robert Calhoun 11/18/2023
谢谢,也在努力。JumboJett OIDC 示例 10 只说“# TODO: what ever was necessary”,因此显示什么是必要的非常有帮助!我发现这个页面很有帮助: developer.okta.com/docs/guides/build-self-signed-jwt/js/main