不太熟悉什么是OAuth2.0的同窗能够参考阮大神的文章, 理解OAuth 2.0 - 阮一峰php
# 受权代码授予类型用于得到访问权限令牌和刷新令牌,并为机密客户进行了优化。 # 因为这是一个基于重定向的流程,客户端必须可以与资源全部者的用户代理(一般是Web)交互浏览器),可以接收传入请求(经过重定向)从受权服务器。 # 受权代码流以下: +----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI ---->| | | User- | | Authorization | | Agent -+----(B)-- User authenticates --->| Server | | | | | | -+----(C)-- Authorization Code ---<| | +-|----|---+ +---------------+ | | ^ v (A) (C) | | | | | | ^ v | | +---------+ | | | |>---(D)-- Authorization Code ---------' | | Client | & Redirection URI | | | | | |<---(E)----- Access Token -------------------' +---------+ (w/ Optional Refresh Token)
# PHP 5.3.9+ composer require bshaffer/oauth2-server-php "^1.10"
-- 你可以使用相应的数据库引擎:MySQL / SQLite / PostgreSQL / MS SQL Server -- 数据库:oauth_test -- 细调过表相关结构,不过你也能够参考官方:http://bshaffer.github.io/oauth2-server-php-docs/cookbook/ DROP TABLE IF EXISTS `oauth_access_tokens`; CREATE TABLE `oauth_access_tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `access_token` varchar(40) NOT NULL, `client_id` varchar(80) NOT NULL, `user_id` varchar(80) DEFAULT NULL, `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `scope` text NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `IDX_ACCESS_TOKEN` (`access_token`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_authorization_codes -- ---------------------------- DROP TABLE IF EXISTS `oauth_authorization_codes`; CREATE TABLE `oauth_authorization_codes` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `authorization_code` varchar(40) DEFAULT '', `client_id` varchar(80) DEFAULT '', `user_id` varchar(80) DEFAULT '0', `redirect_uri` varchar(2000) DEFAULT '', `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `scope` text, `id_token` varchar(1000) DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `IDX_CODE` (`authorization_code`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_authorization_codes -- ---------------------------- -- ---------------------------- -- Table structure for oauth_clients -- ---------------------------- DROP TABLE IF EXISTS `oauth_clients`; CREATE TABLE `oauth_clients` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `client_id` varchar(80) DEFAULT '', `client_secret` varchar(80) DEFAULT '', `client_name` varchar(120) DEFAULT '', `redirect_uri` varchar(2000) DEFAULT '', `grant_types` varchar(80) DEFAULT '', `scope` varchar(4000) DEFAULT '', `user_id` varchar(80) DEFAULT '0', PRIMARY KEY (`id`), KEY `IDX_APP_SECRET` (`client_id`,`client_secret`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_clients -- ---------------------------- INSERT INTO `oauth_clients` VALUES ('1', 'testclient', '123456', '测试demo', 'http://sxx.qkl.local/v2/oauth/cb', 'authorization_code refresh_token', 'basic get_user_info upload_pic', ''); -- ---------------------------- -- Table structure for oauth_jwt -- ---------------------------- DROP TABLE IF EXISTS `oauth_jwt`; CREATE TABLE `oauth_jwt` ( `client_id` varchar(80) NOT NULL, `subject` varchar(80) DEFAULT NULL, `public_key` varchar(2000) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_jwt -- ---------------------------- -- ---------------------------- -- Table structure for oauth_public_keys -- ---------------------------- DROP TABLE IF EXISTS `oauth_public_keys`; CREATE TABLE `oauth_public_keys` ( `client_id` varchar(80) DEFAULT NULL, `public_key` varchar(2000) DEFAULT NULL, `private_key` varchar(2000) DEFAULT NULL, `encryption_algorithm` varchar(100) DEFAULT 'RS256' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_public_keys -- ---------------------------- -- ---------------------------- -- Table structure for oauth_refresh_tokens -- ---------------------------- DROP TABLE IF EXISTS `oauth_refresh_tokens`; CREATE TABLE `oauth_refresh_tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `refresh_token` varchar(40) NOT NULL, `client_id` varchar(80) NOT NULL DEFAULT '', `user_id` varchar(80) DEFAULT '', `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `scope` text, PRIMARY KEY (`id`), UNIQUE KEY `IDX_REFRESH_TOKEN` (`refresh_token`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_scopes -- ---------------------------- DROP TABLE IF EXISTS `oauth_scopes`; CREATE TABLE `oauth_scopes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `scope` varchar(80) NOT NULL DEFAULT '', `is_default` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_scopes -- ---------------------------- INSERT INTO `oauth_scopes` VALUES ('1', 'basic', '1'); INSERT INTO `oauth_scopes` VALUES ('2', 'get_user_info', '0'); INSERT INTO `oauth_scopes` VALUES ('3', 'upload_pic', '0'); -- ---------------------------- -- Table structure for oauth_users 该表是Resource Owner Password Credentials Grant所使用 -- ---------------------------- DROP TABLE IF EXISTS `oauth_users`; CREATE TABLE `oauth_users` ( `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(80) DEFAULT '', `password` varchar(80) DEFAULT '', `first_name` varchar(80) DEFAULT '', `last_name` varchar(80) DEFAULT '', `email` varchar(80) DEFAULT '', `email_verified` tinyint(1) DEFAULT '0', `scope` text, PRIMARY KEY (`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_users -- ---------------------------- INSERT INTO `oauth_users` VALUES ('1', 'qkl', '123456', 'kl', 'q', '', '', '');
Authorization Server 角色html
public function _initialize() { require_once dirname(APP_PATH) . "/vendor/autoload.php"; Autoloader::register(); } private function server() { $pdo = new \PDO('mysql:host=ip;dbname=oauth_test', "user", "123456"); //建立存储的方式 $storage = new \OAuth2\Storage\Pdo($pdo); //建立server $server = new \OAuth2\Server($storage); // 添加 Authorization Code 授予类型 $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($storage)); return $server; }
Authorization Server 角色mysql
User Agent 角色,常规通常基于浏览器git
// 受权页面和受权 public function authorize() { // 该页面请求地址相似: // 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 //获取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 // 这里的$uid不是上面oauth_users表的uid, 是本身系统里的账号的id,你也能够省略该参数 $server->handleAuthorizeRequest($request, $response, $is_authorized, $uid); // if ($is_authorized) { // // 这里会建立Location跳转,你能够直接获取相关的跳转url,用于debug // $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40); // exit("SUCCESS! Authorization Code: $code :: " . $response->getHttpHeader('Location')); // } $response->send(); } /** * 具体基于本身现有的账号系统验证 * @param $request * @return bool */ private function checkLogin($request) { //todo if ($request->request('username') != 'qkl') { return $uid = 0; //login faile } return $uid = 1; //login success }
Authorization Server 角色github
// 生成并获取token public function token() { $server = $this->server(); $server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send(); exit(); }
CLIENT 客户端 角色ajax
# 浏览器访问: 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
# 咱们换行分解下 http://sxx.qkl.local/v2/oauth/authorize? # response_type 固定写死 code response_type=code& # client_id 咱们oauth_clients表的client_id值 client_id=testclient& # state 自定义的参数,随意字符串值 state=xyz& # redirect_uri 回调地址,这里最好是urlencode编码,我这里演示没编码 # 注意这里的redirect_uri须要和oauth_clients表的redirect_uri字段作匹配处理 # redirect_uri字段可存取的方式: # 1. http://sxx.qkl.local/v2/oauth/cb # 2. http://sxx.qkl.local/v2/oauth/cb http://sxx.qkl.local/v2/oauth/cb2 ... 空格分割 redirect_uri=http://sxx.qkl.local/v2/oauth/cb& # response_type 固定写死 code scope=basic%20get_user_info%20upload_pic
CLIENT 客户端 角色sql
// 客户端回调,来自server端的Location跳转到此 // 此处会携带上code和你自定义的state public function cb() { $request = \OAuth2\Request::createFromGlobals(); $url = "http://sxx.qkl.local/v2/oauth/token"; $data = [ 'grant_type' => 'authorization_code', 'code' => $request->query('code'), 'client_id' => 'testclient', 'client_secret' => '123456', 'redirect_uri' => 'http://sxx.qkl.local/v2/oauth/cb' ]; //todo 自定义的处理判断 $state = $request->query('state'); $response = Curl::ihttp_post($url, $data); if (is_error($response)) { var_dump($response); } var_dump($response['content']); }
Authorization Server 角色数据库
// 建立刷新token的server private function refresh_token_server() { $pdo = new \PDO('mysql:host=ip;dbname=oauth_test', "user", "123456"); $storage = new \OAuth2\Storage\Pdo($pdo); $config = [ 'always_issue_new_refresh_token' => true, 'refresh_token_lifetime' => 2419200, ]; $server = new \OAuth2\Server($storage, $config); // 添加一个 RefreshToken 的类型 $server->addGrantType(new \OAuth2\GrantType\RefreshToken($storage, [ 'always_issue_new_refresh_token' => true ])); // 添加一个token的Response $server->addResponseType(new \OAuth2\ResponseType\AccessToken($storage, $storage, [ 'refresh_token_lifetime' => 2419200, ])); return $server; } // 刷新token public function refresh_token() { $server = $this->refresh_token_server(); $server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send(); exit(); }
CLIENT 客户端 角色浏览器
// 客户端模拟refresh_token public function client_refresh_token() { $request = \OAuth2\Request::createFromGlobals(); $url = "http://sxx.qkl.local/v2/oauth/refresh_token"; $data = [ 'grant_type' => 'refresh_token', 'refresh_token' => 'd9c5bee6a4ad7967ac044c99e40496aa2c3d28b4', 'client_id' => 'testclient', 'client_secret' => '123456' ]; $response = Curl::ihttp_post($url, $data); if (is_error($response)) { var_dump($response); } var_dump($response['content']); }
Authorization Server 角色服务器
这里说明下 由于在上面表建立时,我建立了3个socpe[basic,get_user_info,upload_pic]用于测试
上面咱们在浏览器访问的受权地址上也填写了三个权限,因此只要access_token正确在时效内,便可成功访问
// 测试资源 public function res1() { $server = $this->server(); // Handle a request to a resource and authenticate the access token if (!$server->verifyResourceRequest(\OAuth2\Request::createFromGlobals())) { $server->getResponse()->send(); die; } $token = $server->getAccessTokenData(\OAuth2\Request::createFromGlobals()); $scopes = explode(" ", $token['scope']); // todo 这里你能够写成本身规则的scope验证 if (!$this->checkScope('basic', $scopes)) { $this->ajaxReturn(['success' => false, 'message' => '你没有获取该接口的scope']); } $this->ajaxReturn(['success' => true, 'message' => '你成功获取该接口信息', 'token'=>$token['user_id']]); } // 用于演示检测scope的方法 private function checkScope($myScope, $scopes) { return in_array($myScope, $scopes); }
正确的access_token请求:
错误或失效的access_token请求:
Oauth2.0总体没什么具体的技术含量,能够参照规范实现便可
PHP下的Oauth2.0尝试 - OpenID Connect - 后续补位
Oauth2.0 - Authorization Code Grant
使用Authorization_Code获取Access_Token - QQ互联接入
推荐阅读登陆受权方案 - 网站的无密码登陆 - 阮一峰