授权码
如果您曾经使用 Facebook 或 Google 帐户登录过 Web 应用程序,则授权码应该非常熟悉。
使用前提是,用户已经登录到了现有系统。
- WEB 端,可以根据需要强制用户跳转
- APP 端,可以返回特定标记,由 APP 端进行登录处理
第一部分
客户端查询参数发送到授权服务器:
- response_type 响应类型,值为 code
- client_id 客户端标识
- redirect_uri 客户端重定向 URI。此参数是可选的,但如果不发送,用户将被重定向到预注册页面
- scope 以空格分隔的范围列表
- state 使用 CSRF 令牌。此参数是可选的,但强烈建议使用。您应该将 CSRF 令牌的值存储在用户的会话中,以便在用户返回时进行验证。
所有这些参数都将由授权服务器验证。
之后,将要求用户登录到授权服务器并授权客户端。
如果用户授权通过,则会将重定向到客户端指定的 URI 中,并包含以下参数:
- code 授权码
- state 原值返回,用于客户端数据处理、验证等。
第二部分
客户端将以下参数,发送到授权服务器:
- grant_type 授权类型,值为 authorization_code
- client_id 客户端标识
- client_secret 客户端密钥
- redirect_uri 使用相同的重定向 URI
- code 使用查询字符串中的授权码
注意,您需要先解码 code 查询字符串。您可以使用 urldecode($code) 执行此操作。
授权服务器将以下 JSON 数据:
- token_type 值为 Bearer
- expires_in 令牌有效期( 整数)
- access_token 访问令牌
- refresh_token 用于过期前刷新访问令牌。
设置
无论在何处初始化对象,请初始化授权服务器的新实例并绑定存储接口和授权码:
// 初始化库 $clientRepository = new ClientRepository(); // ClientRepositoryInterface 实例 $scopeRepository = new ScopeRepository(); // ScopeRepositoryInterface 实例 $accessTokenRepository = new AccessTokenRepository(); // AccessTokenRepositoryInterface 实例 $authCodeRepository = new AuthCodeRepository(); // AuthCodeRepositoryInterface 实例 $refreshTokenRepository = new RefreshTokenRepository(); // RefreshTokenRepositoryInterface 实例 $privateKey = 'file://path/to/private.key'; //$privateKey = new CryptKey('file://path/to/private.key', 'passphrase'); // 如果私钥有密码 $encryptionKey = 'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'; // 使用 base64_encode(random_bytes(32)) 生成 // 设置授权服务器 $server = new \League\OAuth2\Server\AuthorizationServer( $clientRepository, $accessTokenRepository, $scopeRepository, $privateKey, $encryptionKey ); $grant = new \League\OAuth2\Server\Grant\AuthCodeGrant( $authCodeRepository, $refreshTokenRepository, new \DateInterval('PT10M') // 授权码 10 分钟后过期 ); $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // 刷新令牌 1 月后过期 // 在服务器上启用授权类型验证 $server->enableGrantType( $grant, new \DateInterval('PT1H') // 访问令牌在 1 小时后过期 );
实现
请注意:此处的这些示例演示了 Slim 框架的用法;Slim不是使用此库的必要条件,您只需要生成与 PSR7 兼容的 HTTP 请求和响应的东西。
客户端会将用户重定向到最终页面。
$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 验证 HTTP 请求并返回 AuthorizationRequest 对象 $authRequest = $server->validateAuthorizationRequest($request); // 认证请求对象可以被序列化并保存到用户的会话中 // 此时,您可能希望将用户重定向到登录页 // 用户登录后,在 AuthorizationRequest 上设置用户 $authRequest->setUser(new UserEntity()); // UserEntityInterface 实例 // 在这里,你应该将用户重定向到一个授权页面。 // 这个表单会请求用户批准客户端和请求的作用域 // 用户批准或拒绝,客户端就会更新状态 // (true = 批准, false =拒绝) $authRequest->setAuthorizationApproved(true); // 返回 HTTP 重定向响应 return $server->completeAuthorizationRequest($authRequest, $response); } catch (OAuthServerException $exception) { // 所有 OAuthServerException 实例都可以格式化成 HTTP 响应 return $exception->generateHttpResponse($response); } catch (\Exception $exception) { // 未定义异常 $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } });
客户端将使用授权码请求访问令牌,以便创建 /access_token 端。
$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 试着响应这个请求 return $server->respondToAccessTokenRequest($request, $response); } catch (\League\OAuth2\Server\Exception\OAuthServerException $exception) { // 所有 OAuthServerException 实例都可以格式化成 HTTP 响应 return $exception->generateHttpResponse($response); } catch (\Exception $exception) { // 未定义异常 $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } });
过期
token 过期后,需要通过接口返回的 refresh_token 值来,刷新 token。