PHP `OAuth 2.0` 与 `OpenID Connect` 认证授权流程

各位靓仔靓女,大家好!我是你们今天的认证授权流程讲解员,江湖人称“码农老司机”。今天咱们不飙车,就聊聊在PHP的世界里,OAuth 2.0和OpenID Connect这对好基友是怎么一起愉快玩耍,帮咱们搞定身份验证和授权的。

开场白:为啥要搞这么复杂?

想象一下,你现在想用你的Google账号登录一个新网站(比如一个在线学习平台)。你肯定不希望这个网站直接拿到你的Google账号密码吧?太可怕了!OAuth 2.0和OpenID Connect就是来解决这个问题的,它们能在保证你账号安全的前提下,让第三方应用(比如那个在线学习平台)获取必要的授权,比如读取你的邮箱地址、个人资料等等。

第一章:OAuth 2.0:授权,但别越界!

OAuth 2.0,简单来说,是一个授权框架,它允许第三方应用代表用户访问受保护的资源,而无需用户的用户名和密码。你可以把它想象成一个酒店的门禁卡,你把卡交给酒店,酒店才能帮你打开房间门,而你并不需要把你的房门钥匙(密码)给酒店。

1.1 授权模式(Grant Types):各有所长,选对才行!

OAuth 2.0定义了几种授权模式,不同的模式适用于不同的场景。咱们来挨个看看:

  • 授权码模式(Authorization Code Grant): 这是最常用,也最安全的模式。适用于Web应用和移动应用。

    流程如下:

    1. 用户访问第三方应用(Client),Client将用户重定向到授权服务器(Authorization Server)。
    2. 用户在授权服务器上登录并授权Client访问其资源。
    3. 授权服务器颁发一个授权码(Authorization Code)给Client。
    4. Client使用授权码向授权服务器请求访问令牌(Access Token)。
    5. 授权服务器验证授权码,如果没问题,就颁发访问令牌给Client。
    6. Client使用访问令牌访问受保护的资源服务器(Resource Server)。

    代码示例(简化版,仅供理解):

    // Client端(假设是Web应用)
    // 1. 重定向到授权服务器
    $authorizationUrl = 'https://example.com/oauth/authorize?' . http_build_query([
        'response_type' => 'code',
        'client_id' => 'your_client_id',
        'redirect_uri' => 'https://your_app.com/callback',
        'scope' => 'read:profile',
        'state' => 'random_state_string', // 用于防止CSRF攻击
    ]);
    header('Location: ' . $authorizationUrl);
    exit;
    
    // 2. 授权服务器回调
    if (isset($_GET['code'])) {
        $authorizationCode = $_GET['code'];
    
        // 3. 使用授权码请求访问令牌
        $tokenUrl = 'https://example.com/oauth/token';
        $params = [
            'grant_type' => 'authorization_code',
            'code' => $authorizationCode,
            'redirect_uri' => 'https://your_app.com/callback',
            'client_id' => 'your_client_id',
            'client_secret' => 'your_client_secret', // 注意:客户端密钥要妥善保管!
        ];
    
        $ch = curl_init($tokenUrl);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);
    
        $tokenData = json_decode($response, true);
        $accessToken = $tokenData['access_token'];
    
        // 4. 使用访问令牌访问资源服务器
        $resourceUrl = 'https://example.com/api/profile';
        $ch = curl_init($resourceUrl);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $accessToken,
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $resourceResponse = curl_exec($ch);
        curl_close($ch);
    
        $profileData = json_decode($resourceResponse, true);
        // 处理用户信息
        var_dump($profileData);
    }
  • 隐式授权模式(Implicit Grant): 适用于完全在浏览器端运行的JavaScript应用。 注意:安全性较低,不推荐使用! 因为访问令牌直接通过URL传递,容易被截获。

    流程简化为:

    1. 用户访问第三方应用,Client将用户重定向到授权服务器。
    2. 用户在授权服务器上登录并授权Client访问其资源。
    3. 授权服务器直接将访问令牌(Access Token)作为URL hash fragment返回给Client。
    4. Client使用访问令牌访问受保护的资源服务器。
  • 密码模式(Resource Owner Password Credentials Grant): 适用于Client完全可信,且用户愿意直接将用户名和密码交给Client的情况。 注意:风险较高,不推荐使用!

    流程:

    1. Client直接向用户索取用户名和密码。
    2. Client使用用户名和密码向授权服务器请求访问令牌。
    3. 授权服务器验证用户名和密码,如果没问题,就颁发访问令牌给Client。
    4. Client使用访问令牌访问受保护的资源服务器。
  • 客户端凭据模式(Client Credentials Grant): 适用于Client代表自身访问资源,而不是代表用户。比如,Client需要访问一个公共的API,不需要用户的授权。

    流程:

    1. Client使用自己的客户端ID和客户端密钥向授权服务器请求访问令牌。
    2. 授权服务器验证客户端ID和客户端密钥,如果没问题,就颁发访问令牌给Client。
    3. Client使用访问令牌访问受保护的资源服务器。

1.2 角色划分:谁干啥,分工明确!

OAuth 2.0定义了几个关键角色:

  • 资源拥有者(Resource Owner): 通常是用户,拥有受保护的资源。
  • 客户端(Client): 第三方应用,需要访问资源拥有者的资源。
  • 授权服务器(Authorization Server): 负责验证用户身份,并颁发授权码或访问令牌。
  • 资源服务器(Resource Server): 存储受保护的资源,并验证访问令牌。

1.3 访问令牌(Access Token):通行证在手,资源我有!

访问令牌是Client访问受保护资源的凭证。通常是一个字符串,Client在请求资源时,需要将访问令牌放在HTTP请求头中,通常是Authorization: Bearer <access_token>

1.4 刷新令牌(Refresh Token):续命神器,令牌过期也不怕!

访问令牌通常有有效期,过期后需要重新获取。刷新令牌可以用来获取新的访问令牌,而无需用户再次授权。 注意:刷新令牌也需要妥善保管!

第二章:OpenID Connect:不只是授权,还有身份认证!

OpenID Connect(OIDC)建立在OAuth 2.0之上,它在OAuth 2.0的基础上添加了身份认证的功能。也就是说,OIDC不仅能让第三方应用获取用户的授权,还能验证用户的身份。你可以把OIDC想象成一个身份证,它能证明“我是谁”,以及“我允许你访问我的哪些信息”。

2.1 ID Token:身份的证明,安全可靠!

OIDC引入了一个新的令牌类型:ID Token。ID Token是一个JSON Web Token(JWT),它包含了用户的身份信息,比如用户的ID、姓名、邮箱地址等等。Client可以使用ID Token来验证用户的身份。

2.2 用户信息端点(Userinfo Endpoint):获取更多用户信息!

OIDC定义了一个用户信息端点,Client可以使用访问令牌向该端点请求用户的更多信息。

2.3 流程:OAuth 2.0 + ID Token,双剑合璧!

OIDC的流程和OAuth 2.0的授权码模式类似,只不过在获取访问令牌的同时,还会获取ID Token。

代码示例(简化版):

// Client端(假设是Web应用)
// 1. 重定向到授权服务器
$authorizationUrl = 'https://example.com/oidc/authorize?' . http_build_query([
    'response_type' => 'code',
    'client_id' => 'your_client_id',
    'redirect_uri' => 'https://your_app.com/callback',
    'scope' => 'openid profile email', // 注意:必须包含openid scope
    'state' => 'random_state_string', // 用于防止CSRF攻击
    'nonce' => 'random_nonce_string', // 用于防止重放攻击
]);
header('Location: ' . $authorizationUrl);
exit;

// 2. 授权服务器回调
if (isset($_GET['code'])) {
    $authorizationCode = $_GET['code'];

    // 3. 使用授权码请求访问令牌和ID Token
    $tokenUrl = 'https://example.com/oidc/token';
    $params = [
        'grant_type' => 'authorization_code',
        'code' => $authorizationCode,
        'redirect_uri' => 'https://your_app.com/callback',
        'client_id' => 'your_client_id',
        'client_secret' => 'your_client_secret', // 注意:客户端密钥要妥善保管!
    ];

    $ch = curl_init($tokenUrl);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);

    $tokenData = json_decode($response, true);
    $accessToken = $tokenData['access_token'];
    $idToken = $tokenData['id_token'];

    // 4. 验证ID Token
    // 使用JWT库验证ID Token的签名和声明
    // 例如:使用 Firebase JWT 库
    require_once 'vendor/autoload.php';
    use FirebaseJWTJWT;
    use FirebaseJWTKey;

    $key = 'your_oidc_provider_public_key'; // 从OIDC Provider获取公钥
    $decodedIdToken = JWT::decode($idToken, new Key($key, 'RS256'));

    // 验证 aud(audience)声明,确保ID Token是发给你的Client的
    if ($decodedIdToken->aud !== 'your_client_id') {
        throw new Exception('Invalid audience');
    }

    // 验证 iss(issuer)声明,确保ID Token是来自可信的OIDC Provider的
    if ($decodedIdToken->iss !== 'https://example.com/oidc') {
        throw new Exception('Invalid issuer');
    }

    // 验证 nonce 声明,防止重放攻击
    if ($decodedIdToken->nonce !== 'random_nonce_string') {
        throw new Exception('Invalid nonce');
    }

    // 5. 使用访问令牌访问用户信息端点
    $userinfoUrl = 'https://example.com/oidc/userinfo';
    $ch = curl_init($userinfoUrl);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $accessToken,
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $userinfoResponse = curl_exec($ch);
    curl_close($ch);

    $userData = json_decode($userinfoResponse, true);
    // 处理用户信息
    var_dump($userData);
}

第三章:安全注意事项:防患于未然,安全第一!

  • 保护客户端密钥(Client Secret): 客户端密钥是Client的身份凭证,必须妥善保管,不要泄露给任何人!
  • 使用HTTPS: 所有通信都应该使用HTTPS,防止数据被窃听。
  • 验证重定向URI(Redirect URI): 授权服务器应该验证重定向URI,防止攻击者将用户重定向到恶意网站。
  • 防止CSRF攻击: 使用state参数来防止跨站请求伪造(CSRF)攻击。
  • 防止重放攻击: 在OIDC中使用nonce参数来防止重放攻击。
  • 验证ID Token: Client必须验证ID Token的签名和声明,确保ID Token是有效的。
  • 使用安全的JWT库: 使用经过良好测试和维护的JWT库来处理JWT。

第四章:PHP库推荐:工欲善其事,必先利其器!

  • LeagueOAuth2Client: 一个流行的OAuth 2.0 Client库,提供了对多种OAuth 2.0 Provider的支持。
  • luciferous/oauth2-server: 一个OAuth 2.0 Server库,可以用来构建自己的授权服务器。
  • Firebase JWT: 一个用于处理JWT的库,可以用来验证ID Token。

表格总结:

特性 OAuth 2.0 OpenID Connect
核心功能 授权 授权 + 身份认证
令牌类型 Access Token, Refresh Token Access Token, Refresh Token, ID Token
规范 RFC 6749 OpenID Connect Core 1.0
安全性 依赖于授权模式和安全实践 在OAuth 2.0基础上增加了ID Token的验证机制
适用场景 需要授权访问第三方资源的场景 需要授权访问第三方资源并验证用户身份的场景
是否包含身份信息 是(通过ID Token和Userinfo Endpoint)

尾声:学以致用,举一反三!

今天我们聊了OAuth 2.0和OpenID Connect的基本概念、流程和安全注意事项。希望这些知识能帮助你在实际项目中更好地应用它们。记住,技术是死的,人是活的,要根据实际情况灵活运用,才能真正掌握它们。

最后,祝大家代码无Bug,早日升职加薪! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注