乐趣区

PHP下的Oauth2.0尝试 – OpenID Connect

OpenID Connect
OpenID Connect 简介
OpenID Connect 是基于 OAuth 2.0 规范族的可互操作的身份验证协议。它使用简单的 REST / JSON 消息流来实现, 和之前任何一种身份认证协议相比, 开发者可以轻松集成。OpenID Connect 允许开发者验证跨网站和应用的用户, 而无需拥有和管理密码文件。OpenID Connect 允许所有类型的客户, 包括基于浏览器的 JavaScript 和本机移动应用程序, 启动登录流动和接收可验证断言对登录用户的身份。
OpenID 的历史是什么?
OpenID Connect 是 OpenID 的第三代技术。首先是原始的 OpenID, 它不是商业应用, 但让行业领导者思考什么是可能的。OpenID 2.0 设计更为完善, 提供良好的安全性保证。然而, 其自身存在一些设计上的局限性, 最致命的是其中依赖方必须是网页, 但不能是本机应用程序; 此外它还要依赖 XML, 这些都会导致一些应用问题。OpenID Connect 的目标是让更多的开发者使用, 并扩大其使用范围。幸运的是, 这个目标并不遥远, 现在有很好的商业和开源库来帮助实现身份验证机制。
OIDC 基础
简要而言,OIDC 是一种安全机制, 用于应用连接到身份认证服务器 (Identity Service) 获取用户信息, 并将这些信息以安全可靠的方法返回给应用。在最初, 因为 OpenID1/ 2 经常和 OAuth 协议 (一种授权协议) 一起提及, 所以二者经常被搞混。
OpenID 是 Authentication, 即认证, 对用户的身份进行认证, 判断其身份是否有效, 也就是让网站知道“你是你所声称的那个用户”;OAuth 是 Authorization, 即授权, 在已知用户身份合法的情况下, 经用户授权来允许某些操作, 也就是让网站知道“你能被允许做那些事情”。由此可知, 授权要在认证之后进行, 只有确定用户身份只有才能授权。
(身份验证)+ OAuth 2.0 = OpenID Connect
OpenID Connect 是“认证”和“授权”的结合, 因为其基于 OAuth 协议, 所以 OpenID-Connect 协议中也包含了 client_id、client_secret 还有 redirect_uri 等字段标识。这些信息被保存在“身份认证服务器”, 以确保特定的客户端收到的信息只来自于合法的应用平台。这样做是目的是为了防止 client_id 泄露而造成的恶意网站发起的 OIDC 流程。
在 OAuth 中, 这些授权被称为 scope。OpenID-Connect 也有自己特殊的 scope–openid , 它必须在第一次请求“身份鉴别服务器”(Identity Provider, 简称 IDP)时发送过去。
OpenID Connect 实现
我们的本代码实现建立在 PHP 下的 Oauth2.0 尝试 – 授权码授权(Authorization Code Grant) 文章的代码基础上调整
证书
# 生成私钥 private key
$ openssl genrsa -out privkey.pem 2048

# 私钥生成公钥 public key
$ openssl rsa -in privkey.pem -pubout -out pubkey.pem
调整 server
private function server()
{
$pdo = new \PDO(‘mysql:host=ip;dbname=oauth_test’, “user”, “123456”);
$storage = new \OAuth2\Storage\Pdo($pdo);

$config = [
‘use_openid_connect’ => true, //openid 必须设置
‘issuer’ => ‘sxx.qkl.local’
];

$server = new \OAuth2\Server($storage, $config);

// 第二个参数,必须设置值为 public_key
$server->addStorage($this->getKeyStorage(), ‘public_key’);

// 添加 Authorization Code 授予类型
$server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($storage));

// 添加 Client Credentials 授予类型 一般三方应用都是直接通过 client_id & client_secret 直接请求获取 access_token
$server->addGrantType(new \OAuth2\GrantType\ClientCredentials($storage));

return $server;
}

private function getKeyStorage()
{
$rootCache = dirname(APP_PATH) . “/cert/oauth/”;
$publicKey = file_get_contents($rootCache.’pubkey.pem’);
$privateKey = file_get_contents($rootCache.’privkey.pem’);

// create storage
$keyStorage = new \OAuth2\Storage\Memory(array(‘keys’ => array(
‘public_key’ => $publicKey,
‘private_key’ => $privateKey,
)));

return $keyStorage;
}
授权
public function authorize()
{
// scope 增加 openid
// 该页面请求地址类似:
// http://sxx.qkl.local/v2/oauth/authorize?response_type=code&client_id=testclient&state=xyz&redirect_uri=http://sxx.qkl.local/v2/oauth/cb&scope=basic%20get_user_info%20upload_pic%20openid
// 获取 server 对象
$server = $this->server();
$request = \OAuth2\Request::createFromGlobals();
$response = new \OAuth2\Response();

// 验证 authorize request
// 这里会验证 client_id,redirect_uri 等参数和 client 是否有 scope
if (!$server->validateAuthorizeRequest($request, $response)) {
$response->send();
die;
}

// 显示授权登录页面
if (empty($_POST)) {
// 获取 client 类型的 storage
// 不过这里我们在 server 里设置了 storage,其实都是一样的 storage->pdo.mysql
$pdo = $server->getStorage(‘client’);
// 获取 oauth_clients 表的对应的 client 应用的数据
$clientInfo = $pdo->getClientDetails($request->query(‘client_id’));
$this->assign(‘clientInfo’, $clientInfo);
$this->display(‘authorize’);
die();
}

$is_authorized = true;
// 当然这部分常规是基于自己现有的帐号系统验证
if (!$uid = $this->checkLogin($request)) {
$is_authorized = false;
}

// 这里是授权获取 code,并拼接 Location 地址返回相应
// Location 的地址类似:http://sxx.qkl.local/v2/oauth/cb?code=69d78ea06b5ee41acbb9dfb90500823c8ac0241d&state=xyz
$server->handleAuthorizeRequest($request, $response, $is_authorized, $uid);
if ($is_authorized) {
// 这里会创建 Location 跳转,你可以直接获取相关的跳转 url,用于 debug
$parts = parse_url($response->getHttpHeader(‘Location’));
var_dump($parts);
parse_str($parts[‘query’], $query);

// 拉取 oauth_authorization_codes 记录的信息,包含 id_token
$code = $server->getStorage(‘authorization_code’)
->getAuthorizationCode($query[‘code’]);
var_dump($code);
}
// $response->send();
}

curl 获取
# 使用 HTTP Basic Authentication
$ curl -u testclient:123456 http://sxx.qkl.local/v2/oauth/token -d ‘grant_type=client_credentials’

# 使用 POST Body 请求
$ curl http://sxx.qkl.local/v2/oauth/token -d ‘grant_type=client_credentials&client_id=testclient&client_secret=123456’
postman 获取 access_token

总结
access_token 用于授权
id_token(通常为 JWT) 用于认证

通常我们
首先,需要使用 id_token 登录
然后,你会得到一个 access_token
最后,使用 access_token 来访问授权相关接口。

退出移动版