如何防止 Laravel API 处理查询字符串上的参数?

Posted

技术标签:

【中文标题】如何防止 Laravel API 处理查询字符串上的参数?【英文标题】:How to prevent Laravel API from processing parameters on query-string? 【发布时间】: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.3PHP 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;
    

此方法将主要用于createupdate 操作,如下所示,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-&gt;request-&gt;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');
    
    ...

【问题讨论】:

【参考方案1】:

附加到 url 栏的任何内容都被视为获取请求,并且可以通过 $_GET 超级全局变量获得。我假设 laravel Request 请求将合并 post 和 get 请求,然后当您尝试调用通过 get 或 post 发送的任何参数时,您可以通过

$request->myparam

但如果你只是尝试

$request->request->get('my_param');

你不会得到类似的结果。

:)

【讨论】:

您好,我正在尝试您的建议,但这个“$request->request->post('my_param');”如果这个“$request->request->get('my_param');”,它不起作用,但正在起作用的那个。而且我正在检查“$request->getContent();”这也是只返回body的参数,很好!但使用这种方法“$request->request->get('my_param');”我已经为每个我想从请求中获取的参数写了一个,不是很酷:P,并且使用这个“$request->getContent();”我必须解析结果,因为该方法返回“email=nisevi%40gmail.com&password=password”。 你知道从body获取参数的唯一方法是“$request->request->get('my_param');”吗?和“$request->getContent();” ? 我还发现了一个关于如何从这里github.com/symfony/symfony/issues/13585 获取参数的问题,只是为了添加更多信息...... @nisevi $request-&gt;all() 将给出所有请求数据的key value 对... 是的,但是如果参数在 URL 中,该方法也可以使用...所以如果它们不在正文中,则转到 URL 并且我不想要那个...无论如何我使用您建议的方法$request-&gt;request-&gt;get('my_param'); 请更新您的答案,因为另一个post 它不存在,我会将您的答案标记为正确?

以上是关于如何防止 Laravel API 处理查询字符串上的参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何访问我在 Laravel 4 中手动发送的请求的查询字符串参数?

用户在Laravel中更新密码后自动退出。我怎么防止这个?

错误:防止写入仅在大小写或查询字符串方面与已写入文件不同的文件。反应js laravel

关于Laravel 与 Nginx 限流策略防止恶意请求

如何改进我的查询以防止“PHP 致命错误:允许的内存大小”(laravel 5.2 中的排序和分页)

如何使用NSwag防止查询字符串中的空值