基于ThinkPHP框架搭建OAuth2.0服务
这几天一直在搞OAuth2.0的东西,写SDK啥的,为了更加深入的了解服务端的OAuth验证机制,就自己动手搭了个php下OAuth的环境,并且将它移植到了自己比较熟的tp框架里。
废话不少说,开动。
?
其实网上是有OAuth2.0的php版本的。
你可以在http://code.google.com/p/oauth2-php/?找到源代码,上面实现了PDO和MongoDB的数据模式。这里我也是基于这些代码在TP中进行整合的。
?
好,这里我们可以把下载下来的包解压,把Lib下的OAuth.inc改名为OAuth2.class.php后放到tp核心包下的目录下:
?
/Extend/Library/ORG/OAuth/OAuth2.class.php
?
接下来我们要继承这个类;
在这个目录下新建一个ThinkOAuth2.class.php文件:
?
<?php/** * @category ORG * @package ORG * @author Leyteris * @version 2012.3.16 */// OAUTH2_DB_DSN 数据库连接DSN// OAUTH2_CODES_TABLE 服务器表名称// OAUTH2_CLIENTS_TABLE 客户端表名称// OAUTH2_TOKEN_TABLE 验证码表名称import("ORG.OAuth.OAuth2");class ThinkOAuth2 extends OAuth2 {private $db;private $table;/** * 构造 */public function __construct() {parent::__construct();$this -> db = Db::getInstance(C('OAUTH2_DB_DSN'));$this -> table = array('auth_codes'=>C('OAUTH2_CODES_TABLE'),'clients'=>C('OAUTH2_CLIENTS_TABLE'),'tokens'=>C('OAUTH2_TOKEN_TABLE'));}/** * 析构 */function __destruct() {$this->db = NULL; // Release db connection}private function handleException($e) {echo "Database error: " . $e->getMessage();exit;}/** * * 增加client * @param string $client_id * @param string $client_secret * @param string $redirect_uri */public function addClient($client_id, $client_secret, $redirect_uri) {$time = time();$sql = "INSERT INTO {$this -> table['clients']} "."(client_id, client_secret, redirect_uri, create_time) VALUES ("{$client_id}", "{$client_secret}", "{$redirect_uri}","{$time}")";$this -> db -> execute($sql);}/** * Implements OAuth2::checkClientCredentials() * @see OAuth2::checkClientCredentials() */protected function checkClientCredentials($client_id, $client_secret = NULL) {$sql = "SELECT client_secret FROM {$this -> table['clients']} "."WHERE client_id = "{$client_id}"";$result = $this -> db -> query($sql);if ($client_secret === NULL) {return $result !== FALSE;}//Log::write("checkClientCredentials : ".$result);//Log::write("checkClientCredentials : ".$result[0]);//Log::write("checkClientCredentials : ".$result[0]["client_secret"]);return $result[0]["client_secret"] == $client_secret;}/** * Implements OAuth2::getRedirectUri(). * @see OAuth2::getRedirectUri() */protected function getRedirectUri($client_id) {$sql = "SELECT redirect_uri FROM {$this -> table['clients']} "."WHERE client_id = "{$client_id}"";$result = $this -> db -> query($sql);if ($result === FALSE) {return FALSE;}//Log::write("getRedirectUri : ".$result);//Log::write("getRedirectUri : ".$result[0]);//Log::write("getRedirectUri : ".$result[0]["redirect_uri"]);return isset($result[0]["redirect_uri"]) && $result[0]["redirect_uri"] ? $result[0]["redirect_uri"] : NULL;}/** * Implements OAuth2::getAccessToken(). * @see OAuth2::getAccessToken() */protected function getAccessToken($access_token) {$sql = "SELECT client_id, expires, scope FROM {$this -> table['tokens']} "."WHERE access_token = "{$access_token}"";$result = $this -> db -> query($sql);//Log::write("getAccessToken : ".$result);//Log::write("getAccessToken : ".$result[0]);return $result !== FALSE ? $result : NULL;}/** * Implements OAuth2::setAccessToken(). * @see OAuth2::setAccessToken() */protected function setAccessToken($access_token, $client_id, $expires, $scope = NULL) {$sql = "INSERT INTO {$this -> table['tokens']} "."(access_token, client_id, expires, scope) "."VALUES ("{$access_token}", "{$client_id}", "{$expires}", "{$scope}")";$this -> db -> execute($sql);}/** * Overrides OAuth2::getSupportedGrantTypes(). * @see OAuth2::getSupportedGrantTypes() */protected function getSupportedGrantTypes() {return array(OAUTH2_GRANT_TYPE_AUTH_CODE);}/** * Overrides OAuth2::getAuthCode(). * @see OAuth2::getAuthCode() */protected function getAuthCode($code) {$sql = "SELECT code, client_id, redirect_uri, expires, scope "."FROM {$this -> table['auth_codes']} WHERE code = "{$code}"";$result = $this -> db -> query($sql);//Log::write("getAuthcode : ".$result);//Log::write("getAuthcode : ".$result[0]);//Log::write("getAuthcode : ".$result[0]["code"]);return $result !== FALSE ? $result[0] : NULL;}/** * Overrides OAuth2::setAuthCode(). * @see OAuth2::setAuthCode() */protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {$time = time();$sql = "INSERT INTO {$this -> table['auth_codes']} "."(code, client_id, redirect_uri, expires, scope) "."VALUES ("${code}", "${client_id}", "${redirect_uri}", "${expires}", "${scope}")";$result = $this -> db -> execute($sql); } /** * Overrides OAuth2::checkUserCredentials(). * @see OAuth2::checkUserCredentials() */ protected function checkUserCredentials($client_id, $username, $password){ return TRUE; }}
?
?在这里我们需要创建数据库:
?
CREATE TABLE `oauth_client` ( `id` bigint(20) NOT NULL auto_increment, `client_id` varchar(32) NOT NULL, `client_secret` varchar(32) NOT NULL, `redirect_uri` varchar(200) NOT NULL, `create_time` int(20) default NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;CREATE TABLE `oauth_code` ( `id` bigint(20) NOT NULL auto_increment, `client_id` varchar(32) NOT NULL, `user_id` varchar(32) NOT NULL, `code` varchar(40) NOT NULL, `redirect_uri` varchar(200) NOT NULL, `expires` int(11) NOT NULL, `scope` varchar(250) default NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;CREATE TABLE `oauth_token` ( `id` bigint(20) NOT NULL auto_increment, `client_id` varchar(32) NOT NULL, `user_id` varchar(32) NOT NULL, `access_token` varchar(40) NOT NULL, `refresh_token` varchar(40) NOT NULL, `expires` int(11) NOT NULL, `scope` varchar(200) default NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;?
?
上面的数据库表名可以自己随便定;但是要在config.php配置表名:
?
'OAUTH2_CODES_TABLE'=>'oauth_code','OAUTH2_CLIENTS_TABLE'=>'oauth_client','OAUTH2_TOKEN_TABLE'=>'oauth_token',
?
如果OAuth的服务器不是当前服务器,那就要指定下DSN地址了:
?
?
'OAUTH2_DB_DSN'=>'mysql://root:mima@l:3306/database'
?
?
好了,大致的核心库代码就是如此。接下来要使用它
?
我们创建一个OAuth的Action负责OAuth2的一些验证(OauthAction.class.php)
?
import("ORG.OAuth.ThinkOAuth2");class OauthAction extends Action {private $oauth = NULL;function _initialize(){header("Content-Type: application/json"); header("Cache-Control: no-store");$this -> oauth = new ThinkOAuth2(); } public function index(){ header("Content-Type:application/json; charset=utf-8");$this -> ajaxReturn(null, 'oauth-server-start', 1, 'json'); } public function access_token() {$this -> oauth -> grantAccessToken();}//权限验证public function authorize() {if ($_POST) {$this -> oauth -> finishClientAuthorization($_POST["accept"] == "Yep", $_POST); return;}///表单准备$auth_params = $this -> oauth -> getAuthorizeParams();$this -> assign("params", $auth_params);$this->display();}public function addclient() {if ($_POST && isset($_POST["client_id"]) && isset($_POST["client_secret"]) && isset($_POST["redirect_uri"])) { $this -> oauth -> addClient($_POST["client_id"], $_POST["client_secret"], $_POST["redirect_uri"]);return;}$this->display();}}?
?
这里我们创建了一个私有的oauth对象并在初始化的时候去init它。
?
以上的代码在password那个部分没有做验证,第三种模式需要把ThinkOAuth类中的checkUserCredentials方法进行重写。
?
继续我们写一个受限资源代码。我们这里没有用AOP进行拦截,所以我准备直接用一个基类来模拟拦截。
?
?
import("ORG.OAuth.ThinkOAuth2");class BaseAction extends Action {protected $oauth = NULL;function _initialize(){$this -> oauth = new ThinkOAuth2(); } public function index(){ if(!$this -> oauth -> verifyAccessToken()){ $this -> ajaxReturn(null, 'no,no,no', 0, 'json'); exit(); }$this -> ajaxReturn(null, 'oauth-server', 1, 'json'); } }
?
?接下来直接用一个UserAction来继承它达到受限的目的,如下:
?
?
class UserAction extends BaseAction { public function index(){if(!$this -> oauth -> verifyAccessToken()){ $this -> ajaxReturn(null, 'no,no,no', 0, 'json'); }$this -> ajaxReturn(null, 'oauth-server', 1, 'json'); } }?
?
?
?
最后说明一点,为什么要把user_id耦合进OAuth的表呢?因为我们有时候需要从access_token返查user_id,上面的表就能解决这个问题,但其实还有一种方式是在对于access_token生成的时候自动包含user_id再进行加密,在解码的时候从access_token直接取出user_id就可以了。这里关于user_id和密码验证的都没有去实现,需要后期继承ThinkOAuth2类或者修改checkUserCredentials方法才能实现的。?另外这套东西用在REST模式下我认为更好!