博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PHP下的Oauth2.0尝试 - 授权码授权(Authorization Code Grant)
阅读量:6331 次
发布时间:2019-06-22

本文共 13205 字,大约阅读时间需要 44 分钟。

OAuth 2.0

不太熟悉什么是OAuth2.0的同学可以参考阮大神的文章,

授权码模式(Authorization Code)

# 授权代码授予类型用于获得访问权限令牌和刷新令牌,并为机密客户进行了优化。# 由于这是一个基于重定向的流程,客户端必须能够与资源所有者的用户代理(通常是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)

授权码授权开发

引入OAuth-server包

# 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', '', '', '');

创建server

Authorization Server 角色

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 角色

User Agent 角色,常规一般基于浏览器

// 授权页面和授权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}

创建获取token

Authorization Server 角色

// 生成并获取tokenpublic function token(){    $server = $this->server();    $server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send();    exit();}

授权页面

CLIENT 客户端 角色

# 浏览器访问: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

clipboard.png

授权页面说明

# 我们换行分解下http://sxx.qkl.local/v2/oauth/authorize?# response_type 固定写死 coderesponse_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 固定写死 codescope=basic%20get_user_info%20upload_pic

客户端获取code并请求获取access_token

CLIENT 客户端 角色

// 客户端回调,来自server端的Location跳转到此// 此处会携带上code和你自定义的statepublic 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']);}

clipboard.png

刷新token

Authorization Server 角色

// 创建刷新token的serverprivate 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;}// 刷新tokenpublic function refresh_token(){    $server = $this->refresh_token_server();    $server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send();    exit();}

客户端请求refresh_token

CLIENT 客户端 角色

// 客户端模拟refresh_tokenpublic 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']);}

clipboard.png

scope授权资源

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);}

客户端postman模拟测试

正确的access_token请求:

clipboard.png

错误或失效的access_token请求:

clipboard.png

总结

Oauth2.0整体没什么具体的技术含量,可以参照规范实现即可

后续

附录

转载地址:http://iyboa.baihongyu.com/

你可能感兴趣的文章
Debian下使用OpenLDAP 管理端
查看>>
泛型排序器TComparer
查看>>
9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路...
查看>>
创建符合标准的、有语意的HTML页面——ASP.NET 2.0 CSS Friendly Control Adapters 1.0发布...
查看>>
Adobe驳斥Flash过度耗电论 称HTML5更耗电
查看>>
No!No!No! It's not fashion!
查看>>
艰困之道中学到的经验教训
查看>>
互联网生态建设落地五大挑战——保险科技生态建设 ...
查看>>
进行短视频app开发工作时,可以加入它来保护青少年 ...
查看>>
25G DAC无源高速线缆和25G光模块之间的区别
查看>>
乐乐茶完成近2亿元Pre-A轮融资,祥峰投资领投
查看>>
clickhouse修改时区
查看>>
CSS_定位
查看>>
第二十四章:页面导航(六)
查看>>
百度、长沙加码自动驾驶,湖南阿波罗智行科技公司成立 ...
查看>>
Java面试笔试题大汇总一(最全+详细答案)
查看>>
10 个 Linux 中方便的 Bash 别名
查看>>
[Server] 服务器配置SSH登录邮件通知
查看>>
全新 DOCKER PALS 计划上线,带给您不一样的参会体验! ...
查看>>
Android开发之自定义View(二)
查看>>