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

时间:2014-04-07 16:55:35

标签: php api rest authentication laravel-4

我正在使用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 

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

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

这是我用来管理访问的自定义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)
    {
        //
    }

}