【发布时间】:2017-08-11 04:07:20
【问题描述】:
我想限制我的 Laravel API 在尝试对用户进行身份验证时将参数作为查询字符串处理。我一直在尝试使用 POSTMAN,并且无论我将凭据放在正文上还是作为查询字符串放在 url 中,我都能从我的 API 中获取令牌。
根据 Laravel 文档,我认为这是我想要避免的行为:
Retrieving Input Via Dynamic Properties
您还可以使用 Illuminate\Http\Request 实例。例如,如果您的一个 应用程序的表单包含一个名称字段,您可以访问的值 像这样的字段:
$name = $request->name;当使用动态属性时,Laravel 会 首先在请求有效负载中查找参数的值。如果是 不存在,Laravel 会搜索路由中的字段 参数。
我正在使用 Laravel 5.3 和 PHP 7.1.0
这是使用查询字符串的 POST:
这是使用正文中的参数的 POST:
我已经使用 laravel-cors 配置了我的 CORS:
<?php
return [
'defaults' => [
'supportsCredentials' => false,
'allowedOrigins' => [],
'allowedHeaders' => [],
'allowedMethods' => [],
'exposedHeaders' => [],
'maxAge' => 0,
'hosts' => [],
],
'paths' => [
'v1/*' => [
'allowedOrigins' => ['*'],
'allowedHeaders' => ['Accept', 'Content-Type'],
'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'exposedHeaders' => ['Authorization'],
'maxAge' => 3600,
],
],
];
我的路线(相关路线):
Route::group(
[
'domain' => getenv('API_DOMAIN'),
'middleware' => 'cors',
'prefix' => '/v1',
'namespace' => 'V1'
],
function() {
/* AUTHENTICATION */
Route::post(
'signin',
'AuthenticationController@signin'
)->name('signin');
Route::post(
'signup',
'AuthenticationController@signup'
)->name('signup');
}
);
在列出我的路线php artisan route:list 时,我得到:
------------------------------------------------------------------------------------------------------------------------------------
| Domain | Method | URI | Name | Action | Middleware |
| localhost | POST | api/v1/signin | api.signin | App\Http\Controllers\API\V1\AuthenticationController@signin | api,cors |
| localhost | POST | api/v1/signup | api.signup | App\Http\Controllers\API\V1\AuthenticationController@signup | api,cors |
------------------------------------------------------------------------------------------------------------------------------------
我的AuthenticationController:
<?php
namespace App\Http\Controllers\API\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\Http\Requests;
use JWTAuth;
class AuthenticationController extends Controller
{
public function __construct()
{
$this->middleware('jwt.auth', ['except' => ['signin', 'signup']]);
}
public function signin(Request $request)
{
$credentials = $request->only('email', 'password');
try {
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(
[
'error' => 'invalid_credentials'
],
401
);
}
} catch (JWTException $e) {
return response()->json(
[
'error' => 'could_not_create_token'
],
500
);
}
return response()->json(compact('token'));
}
public function signup(Request $request)
{
try {
$user = User::where(['email' => $request["email"]])->exists();
if($user)
{
return Response::json(
array(
'msg' => "Email {$request->email} already exists"
),
400
);
}
$user = new User;
$user->create($request->input());
return Response::json(
array(
'msg' => 'User signed up.'
)
);
} catch (Exception $exception) {
return Response::json(
array(
'success' => false,
'exception' => $exception
)
);
}
}
}
我的Kernel:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Barryvdh\Cors\HandleCors::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
];
}
我把各自的配置放在config/app.php:
...
/*
* Package Service Providers...
*/
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
Collective\Html\HtmlServiceProvider::class,
Laracasts\Flash\FlashServiceProvider::class,
Prettus\Repository\Providers\RepositoryServiceProvider::class,
\InfyOm\Generator\InfyOmGeneratorServiceProvider::class,
\InfyOm\CoreTemplates\CoreTemplatesServiceProvider::class,
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
Barryvdh\Cors\ServiceProvider::class,
Asvae\ApiTester\ServiceProvider::class,
],
我不想使用dingoapi。
我检查了这些资源:
- JSON Web Token Tutorial: An Example in Laravel and AngularJS
- SERIES: BUILD AN APP WITH LARAVEL5 (BACKEND) AND ANGULARJS (FRONTEND) – PART 1
最后但同样重要的是,我的composer.json:
{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"type": "project",
"require": {
"php": "^7.1.0",
"laravel/framework": "5.3.*",
"barryvdh/laravel-ide-helper": "v2.2.1",
"laravelcollective/html": "v5.3.0",
"infyomlabs/laravel-generator": "5.3.x-dev",
"infyomlabs/core-templates": "5.3.x-dev",
"infyomlabs/swagger-generator": "dev-master",
"jlapp/swaggervel": "2.0.x-dev",
"doctrine/dbal": "2.3.5",
"league/flysystem-aws-s3-v3": "1.0.13",
"tymon/jwt-auth": "0.5.9",
"barryvdh/laravel-cors": "v0.8.2",
"fzaninotto/faker": "~1.4",
"caouecs/laravel-lang": "3.0.19",
"asvae/laravel-api-tester": "^2.0"
},
"require-dev": {
"fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~5.7",
"symfony/css-selector": "3.1.*",
"symfony/dom-crawler": "3.1.*"
},
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall",
"php artisan optimize"
],
"post-update-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"php artisan optimize"
]
},
"config": {
"preferred-install": "dist"
}
}
更新
感谢“Basheer Ahmed”给出的答案,他为我指明了正确的方向,我最终做了一个 Trait 来解析我想要根据请求获得的 body 属性:
<?php
namespace KeepClear\Traits\Controllers;
trait ApiRequest
{
/**
* Parse all attributes and return an array with the present values only.
*
* @param array $attributes
* @param Request $request
*
* @return Array
*/
public function parseBody($attributes, $request)
{
$params = [];
foreach ($attributes as $attribute) {
$value = $request->request->get($attribute);
if ($value) {
$params[$attribute] = $value;
}
}
return $params;
}
}
此方法将主要用于create 和update 操作,如下所示,AddressController:
<?php
namespace KeepClear\Http\Controllers\API\V1;
...
use KeepClear\Traits\Controllers\ApiRequest;
...
class AddressController extends Controller
{
use ApiRequest;
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('jwt.auth');
}
...
/**
* Create address for the specified user.
*
* @param Request $request
* @param String $user_id
*
* @return Response
*/
public function createUserAddress(Request $request, $user_id)
{
try {
$attributes = ['city', 'county_province', 'zip_code'];
$params = $this->parseBody($attributes, $request);
User::find($user_id)->addresses()->create($params);
return Response::json(
array(
'message' => 'The address was successfully created.',
'success' => true
)
);
} catch (Exception $exception) {
return Response::json(
array(
'success' => false,
'exception' => $exception
)
);
}
}
...
/**
* Update address for the specified user.
*
* @param Request $request
* @param String $user_id
* @param String $address_id
*
* @return Response
*/
public function updateUserAddress(Request $request, $user_id, $address_id)
{
try {
$attributes = ['city', 'county_province', 'zip_code'];
$params = $this->parseBody($attributes, $request);
Address::where(["user_id" => $user_id, "id" => $address_id])
->update($params);
return Response::json(
array(
'message' => 'The address was successfully updated.',
'success' => true
)
);
} catch (Exception $exception) {
return Response::json(
array(
'success' => false,
'exception' => $exception
)
);
}
}
...
}
通过这种方式并通过使用$request->request->get('my_param');,我可以确定在测试了该方法的工作原理后,我只能获取主体的属性。
这是AddressController 在这些方法上的测试:
<?php
namespace Tests\Api;
use Tests\TestCase;
...
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Faker\Factory;
...
class AddressControllerTest extends TestCase
{
use ApiTestTrait;
use DatabaseMigrations;
use WithoutMiddleware;
...
public function testMethodCreateUserAddress()
{
$admin = factory(Role::class, 'admin')->create();
$user = $admin->users()->save(factory(User::class)->make());
$uri = 'api/v1/users/' . $user->id . '/addresses';
$faker = Factory::create();
$attributes = array(
'city' => $faker->city,
'county_province' => $faker->state,
'zip_code' => $faker->postcode
);
$this->json('POST', $uri, $attributes)
->seeStatusCode(200)
->seeJsonEquals(
[
'message' => 'The address was successfully created.',
'success' => true
]
);
}
...
public function testMethodUpdateUserAddress()
{
$admin = factory(Role::class, 'admin')->create();
$user = $admin->users()->save(factory(User::class)->make());
$address = $user->addresses()->save(factory(Address::class)->make());
$uri = 'api/v1/users/' . $user->id . '/addresses/' . $address->id;
$attributes = array(
'city' => 'newCity',
'county_province' => 'newCountyProvince',
'zip_code' => 'newZipCode'
);
$this->json('PUT', $uri, $attributes)
->seeStatusCode(200)
->seeJsonEquals(
[
'message' => 'The address was successfully updated.',
'success' => true
]
);
$updated_address = Address::find($address->id);
$this->assertEquals($updated_address->city, 'newCity');
$this->assertEquals(
$updated_address->county_province,
'newCountyProvince'
);
$this->assertEquals($updated_address->zip_code, 'newZipCode');
}
...
}
【问题讨论】: