我正在使用Laravel 4构建REST API。可以从移动应用程序访问此API。我想扩展Laravel内置的身份验证服务以使用令牌。
移动应用程序将特定用户的API调用到端点users/login
。
然后生成一个新令牌并将其插入令牌表中以获取相应的用户ID。请参阅下面令牌表的结构:
id | userId | token | created_at | updated_at | expires_at
我想编写一个身份验证提供程序来覆盖retrieveByID
,retrieveByCredentials
和validateCredentials
函数。
这是移动应用程序在其标题中使用令牌调用任何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
我想错过的是如何在这个类中访问我的模型,以便我可以检索用户并验证令牌。你能帮忙吗?
答案 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)
{
//
}
}