基于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核心包下的目录下:
?
1 | /Extend/Library/ORG/OAuth/OAuth2. class .php
|
Copier après la connexion
?
接下来我们要继承这个类;
在这个目录下新建一个ThinkOAuth2.class.php文件:
?
1 | <?php // 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 ; } 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 ); } 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 ; } 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; } 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; } 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 ); } protected function getSupportedGrantTypes() { return array ( OAUTH2_GRANT_TYPE_AUTH_CODE ); } 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; } 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 ); } protected function checkUserCredentials( $client_id , $username , $password ){ return TRUE; }}
|
Copier après la connexion
?
?在这里我们需要创建数据库:
?
1 | 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;
|
Copier après la connexion
?
?
上面的数据库表名可以自己随便定;但是要在config.php配置表名:
?
1 | 'OAUTH2_CODES_TABLE' => 'oauth_code' , 'OAUTH2_CLIENTS_TABLE' => 'oauth_client' , 'OAUTH2_TOKEN_TABLE' => 'oauth_token' ,
|
Copier après la connexion
?
如果OAuth的服务器不是当前服务器,那就要指定下DSN地址了:
?
?
1 | 'OAUTH2_DB_DSN' => 'mysql://root:[email protected]:3306/database'
|
Copier après la connexion
?
?
好了,大致的核心库代码就是如此。接下来要使用它
?
我们创建一个OAuth的Action负责OAuth2的一些验证(OauthAction.class.php)
?
1 | import( "ORG.OAuth.ThinkOAuth2" ); class OauthAction extends Action { private $oauth = NULL; function _initialize(){ header( "Content-Type: application/json" ); <span style= "white-space: pre;" > </span>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(); }
|
Copier après la connexion
?
?
这里我们创建了一个私有的oauth对象并在初始化的时候去init它。
?
以上的代码在password那个部分没有做验证,第三种模式需要把ThinkOAuth类中的checkUserCredentials方法进行重写。
?
继续我们写一个受限资源代码。我们这里没有用AOP进行拦截,所以我准备直接用一个基类来模拟拦截。
?
?
1 | 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' ); } }
|
Copier après la connexion
?
?接下来直接用一个UserAction来继承它达到受限的目的,如下:
?
?
1 | 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' ); } }
|
Copier après la connexion
?
?
?
?
最后说明一点,为什么要把user_id耦合进OAuth的表呢?因为我们有时候需要从access_token返查user_id,上面的表就能解决这个问题,但其实还有一种方式是在对于access_token生成的时候自动包含user_id再进行加密,在解码的时候从access_token直接取出user_id就可以了。这里关于user_id和密码验证的都没有去实现,需要后期继承ThinkOAuth2类或者修改checkUserCredentials方法才能实现的。?另外这套东西用在REST模式下我认为更好!