cicnavi/oidc-client-php

使用PHP编写的OIDC客户端

v2.0.2 2022-03-04 14:19 UTC

This package is auto-updated.

Last update: 2024-09-04 20:09:10 UTC


README

使用PHP编写的OIDC客户端。它使用OIDC授权代码流进行身份验证。它实现了JWKS公钥使用和自动密钥轮换、缓存机制(默认基于文件),ID令牌验证和声明提取,以及使用'userinfo'获取用户数据。它还可以用于使用为公开客户端设计的PKCE参数模拟授权代码流。

先决条件

PHP环境

  • 请检查composer.json以了解环境要求。
  • ODIC客户端使用PHP会话来处理'state'、'nonce'和'code_verifier'参数的存储和验证。如果会话尚未启动,OIDC客户端将尝试使用php.ini中的会话配置来启动它。

OpenID提供者必须支持

  • 授权代码流
  • OIDC发现URL(包含OIDC元数据的已知URL)
  • 提供JWK密钥的JWKS URI

安装

OIDC客户端作为Composer包提供。在你的项目中,你可以运行

composer require cicnavi/oidc-client-php

客户端实例化

要实例化客户端,你需要准备一个Config实例。首先,准备一个包含以下OIDC配置值的数组,例如

use Cicnavi\Oidc\Config;
use Cicnavi\Oidc\Client;

$config = [
    // Mandatory config items
    // OpenID Provider (OP) well-known configuration URL
    Config::OPTION_OP_CONFIGURATION_URL => 'https://example.org/oidc/.well-known/openid-configuration',
    // Client ID - obtained durign client registration at OP
    Config::OPTION_CLIENT_ID => 'some-client-id',
    // Client Secret - obtained durign client registration at OP
    Config::OPTION_CLIENT_SECRET => 'some-client-secret',
    // Redirect URI to which the authorization server will return auth code
    Config::OPTION_REDIRECT_URI => 'https://your-example.org/callback',
    // Scopes
    Config::OPTION_SCOPE => 'openid profile',
    
    // Optional config items with default values (override them as necessary)
    // Additional time for which the claim 'exp' is considered valid. If false, the check will be skipped.
    //Config::OPTION_ID_TOKEN_VALIDATION_EXP_LEEWAY => 0,
    // Additional time for which the claim 'iat' is considered valid. If false, the check will be skipped.
    //Config::OPTION_ID_TOKEN_VALIDATION_IAT_LEEWAY => 0,
    // Additional time for which the claim 'nbf' is considered valid. If false, the check will be skipped.
    //Config::OPTION_ID_TOKEN_VALIDATION_NBF_LEEWAY => 0,
    // Enable or disable State check
    //Config::OPTION_IS_STATE_CHECK_ENABLED => true,
    // Enable or disable Nonce check
    //Config::OPTION_IS_NONCE_CHECK_ENABLED => true,
    // Set allowed signature algorithms
    //Config::OPTION_ID_TOKEN_VALIDATION_ALLOWED_SIGNATURE_ALGS =>
    //    Config::getIdTokenValidationSupportedSignatureAlgs(), // Or set your own like ['RS256', 'RS512',]
    // Set allowed encryption algorithms
    //Config::OPTION_ID_TOKEN_VALIDATION_ALLOWED_ENCRYPTION_ALGS =>
    //    Config::getIdTokenValidationSupportedEncryptionAlgs(),
    // Should client fetch userinfo claims
    //Config::OPTION_SHOULD_FETCH_USERINFO_CLAIMS => true,
    // Choose if client should act as confidential client or public client
    //Config::OPTION_IS_CONFIDENTIAL_CLIENT => true,
    // If public client, set PKCE code challenge method to use
    //Config::OPTION_PKCE_CODE_CHALLENGE_METHOD => 'S256',
    // Default cache time-to-live in seconds
    //Config::OPTION_DEFAULT_CACHE_TTL => 60 * 60 * 24,
];

确保包含'openid'作用域,以便使用ID令牌提取用户声明。其他作用域是可选的(请参阅你的OpenID提供者的文档)。

接下来,使用之前准备的配置数组创建一个Cicnavi\Oidc\Config实例

$oidcConfig = new Config($config);

现在可以使用配置实例来实例化OIDC客户端

$oidcClient = new Client($oidcConfig);

客户端使用

要启动授权(授权代码流),即启动登录过程,你可以使用authorize()方法

// File: authorize.php
try {
    $oidcClient->authorize();
} catch (\Throwable $exception) {
    // In real app log the error, redirect user and show error message.
    throw $exception;
}

这将启动浏览器重定向到授权服务器,用户将在那里登录。如果登录成功,授权服务器将启动浏览器重定向到已与客户端注册的'redirect_uri'(这是你的回调)。

在回调URI上,你将收到授权代码和状态(如果启用了状态检查)作为GET参数。要使用该授权代码,你可以使用getUserData()方法。此方法将验证状态(如果启用了状态检查)并向令牌端点发送HTTP请求,使用提供的授权代码检索令牌(访问令牌和ID令牌)。然后它将尝试从ID令牌中提取声明(如果返回了ID令牌,即如果客户端配置中使用了'openid'作用域),并使用访问令牌从'userinfo'端点获取用户数据以进行身份验证。

// File: callback.php
try {
    $userData = $oidcClient->getUserData();

    // Log in the user locally, for example:
    if (isset($userData['preferred_username'])) {
        $_SESSION['user'] = $userData['preferred_username'];
        // In real app redirect to another page, show success message...
    } else {
        // In real app redirect to another page, show error message...
    }
    
    // This part is for demo purposes, so we can see returned user data.
    $userDataString = var_export($userData, true);

    $content = <<<EOT
        User data: <br>
        <pre>
        {$userDataString} <br>
        </pre>
        <br>
        <a href="index.php">Back to start page</a>
        EOT;

    require __DIR__ . '/../views/page.php';
} catch (\Throwable $exception) {
    // In real app log the error, redirect user and show error message.
    throw $exception;
}

返回的用户数据将以数组的形式呈现,例如

array (
  'iss' => 'http://example.org',
  'aud' => 'f7f0a46fbd8469a6bb',
  'jti' => 'bc59a823b69945cc8e3731cedc536ed44d3',
  'nbf' => 1593006799,
  'exp' => 1595598799,
  'sub' => 'da4294fb4af275',
  'iat' => 1593006799,
  'family_name' => 'John',
  'given_name' => 'Doe',
  'nickname' => 'jdoe',
  'preferred_username' => 'jdoe@example.org',
  'name' => 'John Doe',
  'email' => 'john.doe@example.org',
  'address' => 'Some organization, Example street 123, HR-10000 Zagreb, Croatia',
  'phone_number' => '123',
  // ...
) 

请注意,某些OpenID提供者(例如,AAI@EduHr联盟)会发送具有多个值的声明,例如

// ... 
'hrEduPersonUniqueID' => 
  array (
    0 => 'jdoe@example.org',
  ),
  'uid' => 
  array (
    0 => 'jdoe',
  ),
  'cn' => 
  array (
    0 => 'John Doe',
  ),
  'sn' => 
  array (
    0 => 'Doe',
  ),
  'givenName' => 
  array (
    0 => 'John',
  ),
  'mail' => 
  array (
    0 => 'john.doe@example.org',
    1 => 'jdoe@example.org',
  ),

关于缓存的通知

OIDC客户端使用缓存以避免在每次客户端使用时向HTTP请求发送以获取OIDC配置内容和JWKS内容。

默认缓存TTL(生存时间)在配置中设置,因此你可以根据需要修改它。如果您需要清除缓存,在使用任何身份验证调用之前,请使用reinitializeCache()客户端实例。

// ... 
$oidcClient = new Client($oidcConfig);
$oidcClient->reinitializeCache();
// ...

默认情况下,OIDC客户端使用基于文件的缓存。这意味着它会在您的系统上使用一个文件夹来存储缓存的文件。为了您的方便,类Cicnavi\Oidc\Cache\FileCache用于实例化一个Cache实例,该实例将在默认的系统'tmp'文件夹中存储文件。在后台,此类将使用cicnavi/simple-file-cache-php包。如果您愿意,可以通过安装提供psr/simple-cache-implementation的相应包(psr/simple-cache-implementation)来利用其他缓存技术(memcached、redis...),并用于OIDC客户端实例化。

以下示例演示了如何使用自定义缓存名称和文件夹路径初始化默认的FileCache实例(请确保文件夹存在,并且可以被Web服务器写入)。

use Cicnavi\Oidc\Cache\FileCache;
// ... other imports

$storagePath = __DIR__ . '/../storage';
$oidcCache = new FileCache($storagePath);

// ... prepare $oidcConfig

// Create client instance using config and cache instances.
$oidcClient = new Client($oidcConfig, $oidcCache);

关于SameSite Cookie属性说明

SameSite Cookie属性在单点登录(SSO)环境中起着重要作用,因为它决定了Cookie如何在第三方上下文中传递。在OIDC授权码流程(此OIDC客户端使用的身份验证流程)期间,在RP和OP之间执行一系列HTTP重定向。

默认情况下,授权码将通过HTTP重定向发送到RP,这意味着用户代理将对RP回调执行GET请求。这意味着SameSite Cookie属性可以设置为'Lax'或'None',但不能设置为'Strict'(如果值为'None',则必须同时设置'Secure'属性)。

运行测试

所有测试都作为Composer脚本提供,因此您可以简单地按以下方式运行它们

$ composer run-script test