OAuth 2.0

一、OAuth2.0 是什么

OAuth 2.0 是一种开放标准的授权协议,用于在不暴露账号密码的情况下,让用户授权第三方应用访问其受保护的资源。
它主要解决的问题是:如何安全地委托访问权限

核心概念:

角色说明
资源所有者(Resource Owner)用户或数据所有者
客户端(Client)第三方应用,需要访问资源
授权服务器(Authorization Server)颁发令牌的服务器,负责验证用户身份并授权
资源服务器(Resource Server)存储用户受保护资源的服务器,验证令牌后提供数据

二、 常用场景

  1. 第三方登录(Social Login)
    用户使用微信、GitHub、Google 登录你的站点,后端获取 Access Token 调用接口获取用户信息。

  2. 开放 API / 平台化
    向合作伙伴提供 API,使用 OAuth 2.0 控制访问权限和作用域。

  3. 微服务内部接口调用
    服务 A 调用服务 B 的接口,通过 Client Credentials 模式获取 Token,避免硬编码密码。

  4. 企业内部 SSO(单点登录)
    多系统统一认证,用户在登录一次后,访问其他系统不需要重复登录。

  5. 前后端分离应用(SPA / Mobile App)
    前端获取授权码,换取 Access Token,安全访问 API,密码不在前端传递。

三、OAuth 2.0 主要流程

授权码模式(Authorization Code Grant) 为例:

  1. 请求授权:客户端引导用户跳转到授权服务器,传入 client_idredirect_uriscope

  2. 用户同意:用户在授权服务器上确认授权。

  3. 返回授权码:授权服务器重定向回客户端,并携带 code

  4. 交换 Access Token:客户端用 code + client_secret 向授权服务器请求 Access Token。

  5. 访问资源:客户端使用 Access Token 调用资源服务器 API。

  6. 刷新 Token(可选):Access Token 到期后使用 Refresh Token 获取新的 Access Token。

流程图示意:

[用户] → [客户端] → [授权服务器] → [用户同意] → [客户端] → [授权服务器] → Access Token → [资源服务器] → 数据

四、PHP 实现示例

4.1 安装依赖

composer require league/oauth2-server

4.2 创建授权服务器

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant\AuthCodeGrant;
use League\OAuth2\Server\CryptKey;

// 数据存储实现可以使用数据库或内存
$privateKey = new CryptKey('file://path/to/private.key');
$encryptionKey = 'base64:'.base64_encode(random_bytes(32));

$server = new AuthorizationServer(
    $clientRepository,    // 客户端存储
    $accessTokenRepository, // Access Token 存储
    $scopeRepository,     // Scope 存储
    $privateKey,
    $encryptionKey
);

// 授权码模式
$grant = new AuthCodeGrant(
    $authCodeRepository,
    $refreshTokenRepository,
    new \DateInterval('PT10M') // 授权码有效期
);

$grant->setRefreshTokenTTL(new \DateInterval('P1M'));
$server->enableGrantType($grant, new \DateInterval('PT1H')); // AccessToken 有效期 1 小时

4.2 发放授权码

use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response;

$request = \Zend\Diactoros\ServerRequestFactory::fromGlobals();
$response = new Response();

try {
    $authResponse = $server->respondToAccessTokenRequest($request, $response);
    echo $authResponse->getBody();
} catch (\League\OAuth2\Server\Exception\OAuthServerException $e) {
    echo $e->getMessage();
}

4.3 验证 Access Token

use League\OAuth2\Server\ResourceServer;
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;

// ResourceServer 初始化
$resourceServer = new ResourceServer(
    $accessTokenRepository,
    new CryptKey('file://path/to/public.key')
);

// 在中间件里验证请求
$middleware = new ResourceServerMiddleware($resourceServer);

通过中间件拦截请求,如果 Access Token 合法,就允许访问 API,否则返回 401

4.4 客户端调用示例(获取用户信息)

$token = 'eyJ0eXAiOiJKV1QiLCJhbGciOi...';
$response = http_client()->get('https://api.example.com/user', [
    'headers' => [
        'Authorization' => 'Bearer ' . $token
    ]
]);

$data = json_decode((string)$response->getBody(), true);
print_r($data);