Vue前端与CSRF中间件Cake后端交互



我正在开发一个带有Vue Frontend&CakePHP 3.8后端。目前,我正在考虑在开始应用程序的实际编码之前设置安全性。我正在关注的一件事是为Cake中的API端点提供CSRF保护。

我正在做的是在created((方法中,让Vue在页面加载时从Cake中检索CSRF令牌。然后Vue将需要在对我的端点的请求中使用CSRF令牌。然后,我用检索到的令牌设置了一个CsrfToken cookie我的CSRF中间件在到达后端时将发送的令牌与此cookie进行比较如果此cookie为空或值不匹配,则会引发InvalidCsrfTokenException错误。

如何在请求参数中添加新条目"_csrfToken">

这是一个通过CSRF中间件检查的请求。。

object(CakeHttpServerRequest) {
    trustProxy => false
    [protected] params => [
        'controller' => 'Customers',
        'action' => 'add',
        'pass' => [],
        'plugin' => null,
        '_matchedRoute' => '/{controller}/{action}/*',
        '_ext' => null,
        '_csrfToken' => '37dfc3327fe642bce88a6aca79c222921b75b752c855b683d9043d8cbbd59ab6ceb44cb3b6b3350aa54d6d1e04d011d0ccec7273150e12b58b4ef23faa47ac3b',
        '_Token' => [
            'unlockedFields' => []
        ],
        'isAjax' => false
    ]

这是我现在的要求问题在于我在$this->中没有设置"_csrfToken";请求->可以与csrf Cookie进行比较的参数

{ "controller": "Placetostays", 
"action": "apitest", 
"pass": [], 
"plugin": null, 
"_matchedRoute": "/{controller}/{action}/*", 
"_ext": null }

在进行表单POST请求时,我收到以下错误。这很正常,因为我的请求参数中没有设置"_csrfToken"。

来自Cake错误日志的错误堆栈跟踪

2020-07-16 09:25:16 Error: [CakeHttpExceptionInvalidCsrfTokenException] Missing CSRF token cookie (C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpMiddlewareCsrfProtectionMiddleware.php:230)
#0 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpMiddlewareCsrfProtectionMiddleware.php(154): CakeHttpMiddlewareCsrfProtectionMiddleware->_validateToken(Object(CakeHttpServerRequest))
#1 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpMiddlewareCsrfProtectionMiddleware.php(122): CakeHttpMiddlewareCsrfProtectionMiddleware->_validateAndUnsetTokenField(Object(CakeHttpServerRequest))
#2 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(65): CakeHttpMiddlewareCsrfProtectionMiddleware->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse), Object(CakeHttpRunner))
#3 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(51): CakeHttpRunner->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#4 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcRoutingMiddlewareRoutingMiddleware.php(168): CakeHttpRunner->run(Object(CakeHttpMiddlewareQueue), Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#5 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(65): CakeRoutingMiddlewareRoutingMiddleware->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse), Object(CakeHttpRunner))
#6 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcRoutingMiddlewareAssetMiddleware.php(88): CakeHttpRunner->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#7 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(65): CakeRoutingMiddlewareAssetMiddleware->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse), Object(CakeHttpRunner))
#8 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcErrorMiddlewareErrorHandlerMiddleware.php(96): CakeHttpRunner->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#9 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(65): CakeErrorMiddlewareErrorHandlerMiddleware->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse), Object(CakeHttpRunner))
#10 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpdebug_kitsrcMiddlewareDebugKitMiddleware.php(53): CakeHttpRunner->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#11 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(65): DebugKitMiddlewareDebugKitMiddleware->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse), Object(CakeHttpRunner))
#12 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpRunner.php(51): CakeHttpRunner->__invoke(Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#13 C:wampserverwwwwamp_projectsholidays_backendvendorcakephpcakephpsrcHttpServer.php(97): CakeHttpRunner->run(Object(CakeHttpMiddlewareQueue), Object(CakeHttpServerRequest), Object(CakeHttpResponse))
#14 C:wampserverwwwwamp_projectsholidays_backendwebrootindex.php(40): CakeHttpServer->run()
#15 {main}
Request URL: /placetostays/apitest
Referer URL: http://localhost:8080/formtesting

Vue前端

<template>
  <div id="formtest">
        
        <div id="formdiv">
          <form v-on:submit.prevent="addPlace">
            <h2>Add Placetostay</h2>
            <br>
            <input class="input" type="text" id="name" v-model="name" placeholder="name"><br>
            <input class="input" type="text" id="city" v-model="city" placeholder="city"><br>    
            <input class="input" type="number" id="postal_code" v-model="postcode" placeholder="postal code"><br> 
            <input class="input" type="text" id="street" v-model="street" placeholder="street"><br>
            <input class="input" type="number" id="house_number" v-model="housenum" placeholder="house number"><br>        
            <input class="input" type="tel" id="tel_number" v-model="telnum" placeholder="phone number"><br><br>             
            <input type="submit" value="Submit">  <button v-on:click="addtoArray">Next</button> 
          </form>           
        </div>
  </div>
</template>
<script>
        
    import $ from 'jquery';   
    
    var token = "";
    
    
    // you will use v-model & data on edits.. 
    export default{
       name :'formtest', 
       data(){
          return{
            user: '',
            name: '',
            city: '',
            postcode: '',
            street: '',
            housenum: '',
            telnum: '',
            post_data: '',
            errors: [],
            data: [],
            token: '',
            total_payload: [],
            }
        },
       
       methods: {
            
            addPlace(){
                 
               this.data[0] = this.name;
               this.data[1] = this.city;   
               this.data[2] = this.postcode;
               this.data[3] = this.street;     
               this.data[4] = this.housenum;
               this.data[5] = this.telnum;   
               // THE QUESTION: HOW DO I SET THIS COOKIE IN MY PARAMS['_csrfToken']?
                        
               this.total_payload.push(this.data);   
               console.log(this.total_payload);            
                        
                        
               var url = 'http://wampprojects/holidays_backend/placetostays/apitest/';
               // this accesses the pass parameter in $this->request->params, does not create new params parameter.. 
               // var url = 'http://wampprojects/holidays_backend/placetostays/apitest/csrftoken:bar';
               // in order for CSRF Middleware & Security Component to work, need to be able to access request parameters 
               // _csrfToken & Token values.. 
               fetch(url, {
                   method: 'POST',
                   mode: 'cors',
                   headers: {
                            'Content-Type': 'application/json'
                          },
                   body: JSON.stringify(this.total_payload),
                        })
                   .then(response => response.json())
                   .then(json_data => this.post_data = json_data) 
                   .catch(error => {
                           console.log("error");
                        });                  
                   this.name = this.city = this.postcode = this.street = this.housenum = this.telnum = '';                        
                        
                    } 
                } 
            },           
       },
       
       created(){
           
            fetch('http://wampprojects/holidays_backend/placetostays/', {
              method: 'GET',
              mode: 'cors',
              headers: {
                'Content-Type': 'application/json',             
              },
            })
            .then(response => response.json())  
            .then(json_data => this.token = json_data) 
            .then(json_data => {
              
               token = json_data;
                // need to initialize the session for when no one is logged in yet..
                // this will be very important!!!  
                this.$session.start();   
                console.log(this.$session.getAll());   
                
                this.$cookies.set('theme', 'default');
                // when to set the cookie value? 
                this.$cookies.set('csrfToken', token['token']);                
                
            })    
            .catch(error => {
               console.log("error");
            }); 
       },
    }
    
</script>

蛋糕控制器后端

public function apitest(){
    $data = $this->request->data;        
    $sendback = "";   
    
    // this check is necessary, will otherwise cause problem at startup 
    if($data){
                    
        // where exactly does the middleware perform this test? when the call arrives @ backend..  
        
        foreach($data as $newplace):
            
            $new_placetostay = $this->Placetostays->newEntity();
            $new_placetostay->name = $newplace[0];
            $new_placetostay->city = $newplace[1]; 
            $new_placetostay->postal_code = $newplace[2];
            $new_placetostay->street = $newplace[3]; 
            $new_placetostay->number = $newplace[4];
            $new_placetostay->tel_number = $newplace[5];   
            
            $this->Placetostays->save($new_placetostay);
            
        endforeach;
                     
    }
    // no automatic view, only data returned
    $this->autoRender = false;
    
    $this->response = $this->response->cors($this->request)
        ->allowOrigin(['http://localhost:8080'])
        ->allowMethods(['GET', 'POST'])
        ->allowHeaders(['*'])
        ->allowCredentials()
        ->exposeHeaders(['Link'])
        ->maxAge(300)
        ->build();          
    
    return $this->response
    ->withType('application/json')
    ->withStringBody(json_encode($parameters));            
    
}

编辑:我正在尝试为控制器"placetostays"创建一个单独的路由作用域,但在对此控制器进行API调用时,它仍然会给我一个CSRF错误。我做错了什么

Router::scope('/', function (RouteBuilder $routes) {
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([
        'httpOnly' => true
    ]));
    $routes->applyMiddleware('csrf');
    
    $routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
    $routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
    
    $routes->fallbacks(DashedRoute::class);
    
});
Router::scope('/apitest', function (RouteBuilder $routes) {
    
    // in this controller I want to have JWT enabled 
    // I would expect CSRF middleware here not to throw an error, since I do not load it for this scope.. 
    
});

我认为您的设置错误。您正在8080端口上开发vuejs,并在单独的环境中运行cakepp。从csrf的角度来看,主机是不同的。您应该:

  1. 在cakewp模板中运行vuejs编译版本,并根据cakephp文档加载所有前端资产。虽然这种方法有效,但它已经过时,而且很蹩脚,因为每次编译vuejs时都必须进行调整和复制,这样就会失去开发服务器的能力。您仍然可以在不加载CsrfMiddleware的CakePhp中使用您的设置并确定用于开发目的的路由的范围。不管怎样,我建议你做以下事情:

  2. 在各自的环境中运行cakepp和vuejs,并用csrf代替jwt。有一个专用的cakephp插件(https://github.com/ADmad/cakephp-jwt-auth)文档中提到的。您应该在cake中创建一个作用域路由,该路由不加载CsrfMiddleWare并加载JWT插件。在控制器中相应地配置身份验证,后端应该可以使用。在Vuejs方面,您需要获得一个令牌,通过设置授权标头

Csrf代币和jwt是不同的,用于不同的目的。Csrf应该在同一台主机上工作。这意味着在您的wamp服务器中,前端和后端应位于同一主机上。你的设置没有。

Jwt令牌构建为在同一主机或不同主机上运行。每个令牌都可以关联到一个用户o在适用的情况下可以是匿名的。

POST、PUT、PATCH和DELETE Http方法应始终受到保护。JWT是一种更好的方法,因为您将处理解耦的前端和后端;"现代";。

随意询问任何

编辑

在routes.php中添加:

Router::prefix('api', function(RouteBuilder $routes) {
$routes->fallbacks(DashedRoute::class);
});

创建src/Controllers/Api/AppController.php并添加:

<?php
namespace AppControllerApi;
use CakeControllerController;
use CakeEventEvent;
class AppController extends Controller
{
public function initialize()
{
    parent::initialize();
    $this->loadComponent('RequestHandler');
$this->loadComponent('Auth', [
        'authenticate' => [
            'Form' => [
                'fields' => [
                    'username' => 'email',
                    'password' => 'password'
                ]
            ],
            'ADmad/JwtAuth.Jwt' => [
                'userModel' => 'Users',
                'fields' => [
                    'email' => 'id'
                ],
                'parameter' => 'token',
                // Boolean indicating whether the "sub" claim of JWT payload
                // should be used to query the Users model and get user info.
                // If set to `false` JWT's payload is directly returned.
                'queryDatasource' => true,
            ]
        ],
        'loginAction' => [
            'controller' => 'Users',
            'action' => 'login',
            'prefix' => 'api'
        ],
         // If unauthorized, return them to page they were just on
        'unauthorizedRedirect' => $this->referer()
    ]);
}
}

以下是发生的事情:

您在router.php中为路由加前缀。CakePhp推断它应该加载名称空间App\Controllers\Api\AppController.php。您使用Jwt信息初始化类,这里假设您使用电子邮件作为用户名进行身份验证,您可以根据需要更改所有这些。

请注意,您还在routes.php中声明了一个回退路由。指向api/*的每个请求都将解析为一个控制器,即"api/users"假设src/Controllers/api/UsersController.php存在,否则将引发一个丢失的controller异常。在每个控制器中,在名称空间App\controllers\Api中,现在您可以控制对不同视图的访问。例如,在src/Controlelrs/Api/UsersController.php中,您需要向Auth对象添加allow约束,并包括不需要JWT Auth:的路由

$this->Auth->allow(['register', 'token']); 

这意味着api/users/register和api/users/令牌路由被设置为覆盖JWT身份验证约束,这是有意义的,因为通常您需要一个端点让用户声明JWT令牌,并且您希望用户注册。

相关内容

  • 没有找到相关文章

最新更新