Laravel 4 - 如何为从移动应用程序访问的 REST API 实现自定义身份验证提供程序


Laravel 4 - How to implement a custom Authentication Provider for a REST API accessed from a mobile app?

我正在使用Laravel 4构建一个REST API。将从移动应用程序访问此 API。我想扩展 Laravel 的内置身份验证服务以使用令牌。

移动应用将特定用户的 API 调用到端点users/login

然后,它会生成一个新令牌,并将其插入到相应用户 ID 的令牌表中。请参阅下面的令牌表的结构:

id | userId | token | created_at | updated_at | expires_at

我想编写一个身份验证提供程序来覆盖retrieveByIDretrieveByCredentialsvalidateCredentials函数。

这允许移动应用程序调用任何 API 方法,其标头中包含令牌,但没有任何用户凭据。

我正在使用HTTP在本地构建它,但它将是HTTPS。

我遵循了不同的教程并编写了如下retrieveByCredentials函数:

public function retrieveByCredentials(array $credentials) {
    $token = token::where('token', '=', $credentials['token'])->take(1)->get();
    if ($token) {
        $user = User::where('user', '=', $token->userId)->get();
        return new GenericUser($user);
    }
    else {
        return false;
    }
}

我宣布

use token;
use user;

在我的AuthUserProvider类开始时,但我想这不是这样做的方法。

代码返回:

Undefined property: Illuminate'Database'Eloquent'Collection::$userId 

我想我缺少的是如何访问我在此类中的模型,以便我可以检索用户并验证令牌。你能帮忙吗?

实现自定义身份验证是重新邀请轮子。我决定使用OAuth 2.0软件包,https://github.com/lucadegasperi/oauth2-server-laravel 并且可以使其满足我的需求

这是我用来管理访问权限的自定义 MODLE

<?php
namespace Api;
use Illuminate'Database'Eloquent;
use Illuminate'Support'Facades'DB;
use Illuminate'Support'Facades'Hash;
use 'Exception;
class ApiAccess extends 'Eloquent {

        protected $table         = 'api_access';

        private $_expiryIntervat = '+20 minutes';

        public function getUserIdentityFromToken($token){
            $arr = explode("-", $token);
            return $arr[0];
        }

        public function getSecurityStringFromToken($token){
              $arr = explode("-", $token);
              return $arr[1];
        }


        public function generateApiAccessToken($id, $email){

               $userIdentity     =  md5( $email."|".$id );
               $securityString   =  bin2hex( openssl_random_pseudo_bytes(16) );
               $accessToken      =  $userIdentity . "-" . $securityString;
               $now              =  date("Y-m-d H:i:s");
               $expiry           =  date('Y-m-d H:i:s', strtotime( $this->_expiryIntervat, strtotime($now)));

               if( $this -> isTokenExist($id) ) {
                     $data  =  array(
                        'access_key'        => $userIdentity, 
                        'access_token'      => $securityString, 
                        'last_request_time' => $now,          
                        'token_expiry_time' => $expiry
                     );
                     $this -> updateAccessSession($data, $id);
               } else {
                     $data = array(
                        'user_id'           => $id,
                        'email'             => $email,
                        'access_key'        => trim($userIdentity), 
                        'access_token'      => trim($securityString), 
                        'last_request_time' => $now,          
                        'token_expiry_time' => $expiry
                     ) ; 
                     $this -> saveAccessSession($data);
               }
               return $accessToken;
        } 
        public function saveAccessSession($data){
             ApiAccess :: insert( $data );
        } 
        public function updateAccessSession($data, $id){
             DB :: table('api_access') -> where('user_id', '=', $id) -> update($data);
        } 



        public function isValidFormat($token){
              $arr = explode( "-", $token );
              if( count($arr) == 2 ) 
                  return true;
              else 
                  throw new Exception('Token is not in valid format.', 502);
        }
        public function isValidApiAccessToken($userIdentity, $securityString, $checkTokenActiveStatus = true ){
                $user = ApiAccess ::  select( array( 'id', 'user_id', 'email', 'access_key', 'access_token', 'token_expiry_time as expiry') )
                                        -> where ( 'access_key',   '=', $userIdentity )
                                        -> where ( 'access_token', '=', $securityString )
                                        -> first();

                if(!is_object($user))
                    throw new 'Exception('Token does not exist for user.', 503);
                $user = $user -> toArray();
                if($checkTokenActiveStatus){
                     if( ! $this -> isTokenActive( strtotime($user['expiry']) ) )
                        throw new 'Exception('Token is expired.', 504);
                }   

                return $user;
        }
        private function isTokenActive( $expiry ){
                $now  = strtotime(date("Y-m-d H:i:s"));
                return $status =   ($now > $expiry) ? false : true ;
        }
        private function isTokenExist($id){
            $token =    ApiAccess ::  select( array('id') ) -> where ( 'user_id', '=', $id ) -> first();
            return (!$token)? false : true ;                             
        }


        public function extendApiAccessTokenExpiry($id){
                $now              =  date("Y-m-d H:i:s");
                $expiry           =  date('Y-m-d H:i:s', strtotime( $this->_expiryIntervat, strtotime($now)));
        $data  =  array(
                    'last_request_time' => $now,          
                    'token_expiry_time' => $expiry
                 );
                $this -> updateAccessSession($data, $id);
        }

        public function expireApiAccessToken($id){
                $now              =  date("Y-m-d H:i:s");
                $expiry           =  date('Y-m-d H:i:s', strtotime( "-20 minutes", strtotime($now)));
        $data  =  array(
                    'last_request_time' => $now,          
                    'token_expiry_time' => $expiry
                 );
                $this -> updateAccessSession($data, $id);
        }


}

下面是构建响应的类

<?php
namespace Api;

use 'Exception;
class ApiResponse {

        /**
         * 
         * The over all response status.
         * 
         * @var boolean 
         */
        protected $_status                = true; 


        /**
         * 
         * The over all response status.
         * 
         * its value can be 200 in case of success and 500 in case of any error
         * 
         * @var int 
         */
        protected $_statusCode            = 200; 

        /**
         * 
         * If Errors are detected, these are set in the below array.
         * 
         * Formate : array( 0 => array('code','message'))
         * 
         * @var array 
         */
        protected $_error                = array(); 


        /**
         * 
         * Success Messages will save in this array.
         * 
         * Formate : array( 0 => array('code','message'))
         * 
         * @var array 
         */
        protected $_success              = array(); 


        /**
         * 
         * Success Messages will save in this array.
         * 
         * @var array 
         */
        protected $_data                 = array(); 


       /**
        * Build the final response for api calls
        * 
        * 
        * @return array
        */

        public function build(){
           if( $this -> countError() > 0 )
               $this -> setStatus (false, 500);

           $response                    =   array();
           $response['status']          =   $this -> getStatus();
           $response['statusCode']      =   $this -> getStatusCode();         
           $response['messages']        =   ( $this -> getStatus() ) ? $this -> getSuccess() : $this -> getError();

           if( $this -> countData() > 0 && $this -> getStatus() )
                $response['data']       =   $this -> getData();

           return $response;
        }




      //-----------------------------------------------------------------------------------------//
      //----                                    Setters                                      ----//
      //-----------------------------------------------------------------------------------------//


      /**
       * 
       * Use only in case when some error is found during processing api request.
       * 
       * @param boolean $status true or false
       * @param int     $statusCode 200 or 500
       */ 
       public function setStatus($status, $statusCode){
           $this -> _status      =   $status;
           $this -> _statusCode  =   $statusCode;
       }

       /**
        * 
        * @param int    $code
        * @param string $message
        */
       public function setError($code, $message){
           $count    =   $this -> countError();
           $this -> _error[$count]['code']      =   $code;
           $this -> _error[$count]['message']   =   $message;
       }

      /**
       * 
       * @param int     $code
       * @param string  $message
       */
       public function setSuccess($code, $message){
           $count    =   $this -> countSuccess();
           $this -> _success[$count]['code']      =   $code;
           $this -> _success[$count]['message']   =   $message;
       }

       /**
        * 
        * @param string         $key
        * @param string / array $value
        */
       public function setData($key,$value){
           $this -> _data[$key]      =   $value;
       }




      //-----------------------------------------------------------------------------------------//
      //----                                    Getters                                      ----//
      //-----------------------------------------------------------------------------------------//

      /**
       * 
       * Return the over all status fo the request
       * 
       * @return boolean
       */
       private function getStatus(){
           return $this -> _status;
       }

      /**
       * 
       * Status code for the request.
       * 
       * @return int value (200 or 500)
       */
       private function getStatusCode(){
           return $this -> _statusCode ;
       }

      /**
       * 
       * Gets the array of errors, each have a code (int) and message (string)
       * 
       * @return array
       */
       private function getError(){
           return $this -> _error;
       }

      /**
       * 
       * Gets the array of success, each have a code (int) and message (string)
       * 
       * @return array
       */
       private function getSuccess(){
           return $this -> _success;
       }

      /**
       * 
       * Gets the data to be included in response
       * 
       * @return array
       */
       private function getData(){
           return $this -> _data;
       }




      //-----------------------------------------------------------------------------------------//
      //----                                Counter Functions                                ----//
      //-----------------------------------------------------------------------------------------//

      /**
       * 
       * Count number of errors found.
       * 
       * @return int
       */
       private function countError(){
           return count( $this -> _error);
       }

      /**
       * 
       * Count number of success found.
       * 
       * @return int
       */
       private function countSuccess(){
           return count( $this -> _success);
       }

      /**
       * 
       * Count elements in data array.
       * 
       * @return int
       */
       private function countData(){
           return count( $this -> _data);
       }

}

下面是我从中释放所有

控制器的基本控制器
<?php
namespace Api;
use 'Route;
use 'Input;
use 'Exception;
use Api'ApiAccess;
use Api'ApiResponse;
class BaseController extends 'Controller {

   /**
    *
    * @var string 
    */ 
    private $_accessToken;

   /**
    *
    * @var string 
    */ 
    private $_accessTokenUserIdentity;


   /**
    *
    * @var string 
    */ 
    private $_accessTokenSecurityString;


   /**
    *
    * @var array 
    */ 
    protected $user;

   /**
    *
    * @var ApiAccess object of api access class 
    */
    protected $apiAccess;


    /**
     *
     * @var ApiResponse object of api response class.  
     */
    protected $apiResponse;

    /**
     *
     * @var boolean whwather to check token expiry or not while validating token.  
     */
    protected $_checkTokenActiveStatus = true;

    /**
     * Checks if request is not login request, process token for various validation before proceeding on next step.
     * 
     * Creates object of for access handler class and response builder class. 
     *
     * @return void
     */
    public function __construct()
    {
        $this -> apiAccess        =   New ApiAccess();
        $this -> apiResponse      =   new ApiResponse();
        if( Route::currentRouteName() == "api.logout.index" )
            $this -> _checkTokenActiveStatus = false;
        if( Route::currentRouteName() != "api.login.store" ){
            $this -> _accessToken =   Input::get('accessToken');
            try {
                 if ( $this -> apiAccess -> isValidFormat ( $this -> _accessToken ) ) {
                      $this -> _accessTokenUserIdentity      =   $this -> apiAccess -> getUserIdentityFromToken   ( $this -> _accessToken );
                      $this -> _accessTokenSecurityString    =   $this -> apiAccess -> getSecurityStringFromToken ( $this -> _accessToken );
                      try{
                          if ( $this -> user = $this -> apiAccess -> isValidApiAccessToken(  $this -> _accessTokenUserIdentity, $this -> _accessTokenSecurityString, $this -> _checkTokenActiveStatus ) ) {
                                 $this -> apiAccess -> extendApiAccessTokenExpiry ( $this -> user['user_id'] );                        
                          }
                      } catch ('Exception $e) {
                          $this -> apiResponse -> setError ( $e -> getCode(), $e -> getMessage() ) ;
                      }
                 } 
            } catch ('Exception $e) {
                 $this -> apiResponse -> setError ( $e -> getCode(), $e -> getMessage() ) ;
            }
       }
    }
}

最后是示例登录控制器

<?php
namespace Api;
use 'Route;
use 'Input;
use 'Sentry;
use 'User;
use Ldap'Ldap;
class LoginController extends BaseController {
        /**
         *
         * @var String value sentry or ldap 
         */
        private $_authType = 'sentry'; 

    /**
     * Display a listing of the resource.
     *
     * @return Response
     */
    public function index()
    {
    }
    /**
     * Show the form for creating a new resource.
     *
     * @return Response
     */
    public function create()
    {
        //
    }
    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store()
    {
                // content type in http request : application/x-www-form-urlencoded
        $input = Input::all();

        try {

                    if ( $this -> _authType == 'sentry' )
                        $user = Sentry::authenticate(array('email'=> $input['email'], 'password'=> $input['password']), false);

                    if ( $this -> _authType == 'ldap' ) {
                        $ldap       = new Ldap();
                        $response   = $ldap -> authenticate( array('username'=> $input['email'], 'password'=> $input['password']) );
                        if($response -> status == false)
                            throw new 'Exception($response -> messages[0] -> message, $response -> messages[0] -> code);
                        if(!$user = Sentry::findUserByLogin( $response -> data -> user -> login ))
                            throw new 'Exception("User is not registered to website. Please first go to our website and login there  and complete your profile to use mobile app.", 512);
                    } 

                    if ($user) {    
                            $admin = 0;
                            if ( $permissions = $user->getPermissions() )
                                $admin = ( isset($permissions['admin']) && $permissions['admin'] == 1 ) ? 1 : 0;
                            $accessToken   =  $this -> apiAccess -> generateApiAccessToken( $user->id, $user->email  ); 
                            $this -> apiResponse -> setSuccess(201, 'Successfully Authenticated.');
                            $this -> apiResponse -> setData('userId', $user->id);
                            $this -> apiResponse -> setData('isAdmin', $admin);
                            $this -> apiResponse -> setData('accessToken', $accessToken);
                            return $this -> apiResponse -> build();
                    }
                    $this -> apiResponse -> setError(505, 'Authentication Failed');
        }
        catch ('Cartalyst'Sentry'Users'LoginRequiredException $e) {
                        $this -> apiResponse -> setError(506, 'Email Required.');
        }
        catch ('Cartalyst'Sentry'Users'PasswordRequiredException $e) {
                        $this -> apiResponse -> setError(507, 'Password is required.');
        }
        catch ('Cartalyst'Sentry'Users'WrongPasswordException $e) {
                        $this -> apiResponse -> setError(508, 'Password is not correct.');
        }
        catch ('Cartalyst'Sentry'Users'UserNotFoundException $e) {
                        $this -> apiResponse -> setError(509, 'User not found.');
        }
        catch ('Cartalyst'Sentry'Users'UserNotActivatedException $e) {
                        $this -> apiResponse -> setError(510, 'User not activated yet.');
        }
        // The following is only required if throttle is enabled
        catch ('Cartalyst'Sentry'Throttling'UserSuspendedException $e) {
                        $this -> apiResponse -> setError(511, 'This user is suspended.');
        }
        catch ('Cartalyst'Sentry'Throttling'UserBannedException $e) {
                        $this -> apiResponse -> setError(512, 'This user is banned.');  
        }   
                catch ('Exception $e){
                       $this -> apiResponse -> setError ( $e -> getCode(), $e -> getMessage() ) ;
                }
                return $this -> apiResponse -> build();
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function destroy($id)
    {
        //
    }
}