我正在开发一个带有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的角度来看,主机是不同的。您应该:
-
在cakewp模板中运行vuejs编译版本,并根据cakephp文档加载所有前端资产。虽然这种方法有效,但它已经过时,而且很蹩脚,因为每次编译vuejs时都必须进行调整和复制,这样就会失去开发服务器的能力。您仍然可以在不加载CsrfMiddleware的CakePhp中使用您的设置并确定用于开发目的的路由的范围。不管怎样,我建议你做以下事情:
-
在各自的环境中运行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令牌,并且您希望用户注册。