在 Laravel 6、Module、Lighthouse 中实现 安全 验证 的流程(使用验证器类来支持复杂的验证规则)
1、当请求响应成功时的结构。如图1
mutation {
onlineStoreThemeAssetCreate(
input: { themeId: "vogue", content: "string", key: "string" }
) {
themeAsset {
id
themeId
content
key
mimeType
category
schema
createdAt
updatedAt
deletable
renameable
updatable
}
}
}
{
"data": {
"onlineStoreThemeAssetCreate": {
"themeAsset": {
"id": "653",
"themeId": "vogue",
"content": "string",
"key": "string",
"mimeType": "text/x-php",
"category": "unknown",
"schema": null,
"createdAt": "2022-02-25 01:49:53",
"updatedAt": "2022-02-25 01:49:53",
"deletable": false,
"renameable": false,
"updatable": false
}
}
}
}
2、但是,现阶段并未针对请求参数进行安全验证。参考:https://lighthouse-php.com/master/security/validation.html#single-arguments 。Lighthouse 允许您在查询和变更中使用 Laravel 的验证。如图2
3、此 GraphQL API 的变更虽然在输入对象中仅有 3 个参数,但是验证规则比较复杂。包含如下规则:
(1)验证表中是否存在 themeId: “vogue” 的记录,如果不存在,验证失败;
SELECT * FROM `object_store`.`theme_asset` WHERE `theme_id` = 'vogue' LIMIT 0,1
(2)验证 key: “string” 的格式,其必须为一个有效的相对路径,例:assets/iconfont/iconfont.css。其值等于 string ,验证失败;
(3)验证 key: “string” 的格式,其为一个有效的相对路径后,其文件后缀名必须属于一个预先定义的数组中,例:[‘json’, ‘css’, ‘js’];
(4)验证表中 themeId: “vogue”, key: “string” 记录的唯一性,如果已存在,验证失败;
SELECT * FROM `object_store`.`theme_asset` WHERE `theme_id` = 'vogue' AND `asset_key` = 'string' LIMIT 0,1
(5)验证 content: “string” 的格式,例:当 key 的值的文件后缀名为 .json 时,需要验证 content 的值格式为 json 格式;
(6)mime_type 的值需要基于 key 的值转换得出;
(7)category 的值需要基于 key 的值转换得出;
4、使用验证器类来支持复杂的验证规则。Lighthouse 对验证器类使用简单的命名约定,只需使用输入类型的名称并附加 Validator 。最终生成文件:/app/GraphQL/Validators/CreateOnlineStoreThemeAssetInputValidator.php
PS E:\wwwroot\lighthouse-tutorial> php artisan lighthouse:validator CreateOnlineStoreThemeAssetInputValidator Validator created successfully.
5、由于现在需要在 Module ThemeStore 中使用此验证器类,将文件:/app/GraphQL/Validators/CreateOnlineStoreThemeAssetInputValidator.php 剪切至:/Modules/ThemeStore/Validators/CreateOnlineStoreThemeAssetInputValidator.php
<?php
namespace Modules\ThemeStore\Validators;
use Nuwave\Lighthouse\Validation\Validator;
class CreateOnlineStoreThemeAssetInputValidator extends Validator
{
/**
* Return the validation rules.
*
* @return array<string, array<mixed>>
*/
public function rules(): array
{
return [
'themeId' => [
'exists:theme_asset,theme_id'
],
];
}
}
6、编辑文件:/Modules/ThemeStore/Resources/graphql/theme_asset.graphql,修改输入对象,使用 @validator 指令,以指定验证器类
input OnlineStoreThemeAssetCreateInput @validator(class: "Modules\\ThemeStore\\Validators\\CreateOnlineStoreThemeAssetInputValidator") {
"主题ID"
themeId: String!,
"内容"
content: String!,
"路径,相对于主题的路径,如 pages/index.blade.php"
key: String!,
}
7、测试验证规则是否有效,确定有效。如图3
mutation {
onlineStoreThemeAssetCreate(
input: { themeId: "vogue1", content: "string", key: "string" }
) {
themeAsset {
id
themeId
content
key
mimeType
category
schema
createdAt
updatedAt
deletable
renameable
updatable
}
}
}
{
"errors": [
{
"message": "Validation failed for the field [onlineStoreThemeAssetCreate].",
"extensions": {
"validation": {
"input.themeId": [
"The selected input.theme id is invalid."
]
},
"category": "validation"
},
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"onlineStoreThemeAssetCreate"
],
"trace": [
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\ArgTraversalDirective.php",
"line": 29,
"call": "Nuwave\\Lighthouse\\Validation\\ValidateDirective::Nuwave\\Lighthouse\\Validation\\{closure}(null, array(1), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\TrimDirective.php",
"line": 56,
"call": "Nuwave\\Lighthouse\\Schema\\Directives\\ArgTraversalDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(null, array(1), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Factories\\FieldFactory.php",
"line": 99,
"call": "Nuwave\\Lighthouse\\Schema\\Directives\\TrimDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(null, array(1), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 623,
"call": "Nuwave\\Lighthouse\\Schema\\Factories\\FieldFactory::Nuwave\\Lighthouse\\Schema\\Factories\\{closure}(null, array(1), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 550,
"call": "GraphQL\\Executor\\ReferenceExecutor::resolveFieldValueOrError(instance of GraphQL\\Type\\Definition\\FieldDefinition, instance of GraphQL\\Language\\AST\\FieldNode, instance of Closure, null, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 474,
"call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: Mutation, null, instance of ArrayObject(1), array(1))"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 857,
"call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'onlineStoreThemeAssetCreate')"
},
{
"call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'onlineStoreThemeAssetCreate')"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 859,
"function": "array_reduce(array(1), instance of Closure, array(0))"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 490,
"call": "GraphQL\\Executor\\ReferenceExecutor::promiseReduce(array(1), instance of Closure, array(0))"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 263,
"call": "GraphQL\\Executor\\ReferenceExecutor::executeFieldsSerially(GraphQLType: Mutation, null, array(0), instance of ArrayObject(1))"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
"line": 215,
"call": "GraphQL\\Executor\\ReferenceExecutor::executeOperation(instance of GraphQL\\Language\\AST\\OperationDefinitionNode, null)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\Executor.php",
"line": 156,
"call": "GraphQL\\Executor\\ReferenceExecutor::doExecute()"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\GraphQL.php",
"line": 162,
"call": "GraphQL\\Executor\\Executor::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\GraphQL.php",
"line": 94,
"call": "GraphQL\\GraphQL::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null, array(29))"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
"line": 268,
"call": "GraphQL\\GraphQL::executeQuery(instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null, array(29))"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
"line": 203,
"call": "Nuwave\\Lighthouse\\GraphQL::executeParsedQuery(instance of GraphQL\\Language\\AST\\DocumentNode, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
"line": 162,
"call": "Nuwave\\Lighthouse\\GraphQL::parseAndExecuteQuery('mutation {\n onlineStoreThemeAssetCreate(\n input: { themeId: \"vogue1\", content: \"string\", key: \"string\" }\n ) {\n themeAsset {\n id\n themeId\n content\n key\n mimeType\n category\n schema\n createdAt\n updatedAt\n deletable\n renameable\n updatable\n }\n }\n}', instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
"line": 121,
"call": "Nuwave\\Lighthouse\\GraphQL::executeOperation(instance of GraphQL\\Server\\OperationParams, instance of Nuwave\\Lighthouse\\Schema\\Context)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Utils.php",
"line": 99,
"call": "Nuwave\\Lighthouse\\GraphQL::Nuwave\\Lighthouse\\{closure}(instance of GraphQL\\Server\\OperationParams)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
"line": 120,
"call": "Nuwave\\Lighthouse\\Support\\Utils::applyEach(instance of Closure, instance of GraphQL\\Server\\OperationParams)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Http\\Controllers\\GraphQLController.php",
"line": 32,
"call": "Nuwave\\Lighthouse\\GraphQL::executeOperationOrOperations(instance of GraphQL\\Server\\OperationParams, instance of Nuwave\\Lighthouse\\Schema\\Context)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
"line": 48,
"call": "Nuwave\\Lighthouse\\Support\\Http\\Controllers\\GraphQLController::__invoke(instance of Illuminate\\Http\\Request, instance of Nuwave\\Lighthouse\\GraphQL, instance of Illuminate\\Events\\Dispatcher, instance of Laragraph\\Utils\\RequestParser, instance of Nuwave\\Lighthouse\\Execution\\SingleResponse, instance of Nuwave\\Lighthouse\\Execution\\ContextFactory)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
"line": 219,
"call": "Illuminate\\Routing\\ControllerDispatcher::dispatch(instance of Illuminate\\Routing\\Route, instance of Nuwave\\Lighthouse\\Support\\Http\\Controllers\\GraphQLController, '__invoke')"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
"line": 176,
"call": "Illuminate\\Routing\\Route::runController()"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 681,
"call": "Illuminate\\Routing\\Route::run()"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 130,
"call": "Illuminate\\Routing\\Router::Illuminate\\Routing\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Http\\Middleware\\AttemptAuthentication.php",
"line": 34,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Nuwave\\Lighthouse\\Support\\Http\\Middleware\\AttemptAuthentication::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Http\\Middleware\\AcceptJson.php",
"line": 27,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Nuwave\\Lighthouse\\Support\\Http\\Middleware\\AcceptJson::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 105,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 683,
"call": "Illuminate\\Pipeline\\Pipeline::then(instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 658,
"call": "Illuminate\\Routing\\Router::runRouteWithinStack(instance of Illuminate\\Routing\\Route, instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 624,
"call": "Illuminate\\Routing\\Router::runRoute(instance of Illuminate\\Http\\Request, instance of Illuminate\\Routing\\Route)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 613,
"call": "Illuminate\\Routing\\Router::dispatchToRoute(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
"line": 170,
"call": "Illuminate\\Routing\\Router::dispatch(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 130,
"call": "Illuminate\\Foundation\\Http\\Kernel::Illuminate\\Foundation\\Http\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\barryvdh\\laravel-debugbar\\src\\Middleware\\InjectDebugbar.php",
"line": 67,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Barryvdh\\Debugbar\\Middleware\\InjectDebugbar::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\app\\Http\\Middleware\\ChangeAppUrlMiddleware.php",
"line": 23,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "App\\Http\\Middleware\\ChangeAppUrlMiddleware::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
"line": 21,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
"line": 21,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize.php",
"line": 27,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode.php",
"line": 63,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\fideloper\\proxy\\src\\TrustProxies.php",
"line": 57,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Fideloper\\Proxy\\TrustProxies::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\dingo\\api\\src\\Http\\Middleware\\Request.php",
"line": 111,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 171,
"call": "Dingo\\Api\\Http\\Middleware\\Request::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 105,
"call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
"line": 145,
"call": "Illuminate\\Pipeline\\Pipeline::then(instance of Closure)"
},
{
"file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
"line": 110,
"call": "Illuminate\\Foundation\\Http\\Kernel::sendRequestThroughRouter(instance of Illuminate\\Http\\Request)"
},
{
"file": "E:\\wwwroot\\object\\public\\index.php",
"line": 57,
"call": "Illuminate\\Foundation\\Http\\Kernel::handle(instance of Illuminate\\Http\\Request)"
}
]
}
],
"data": {
"onlineStoreThemeAssetCreate": null
}
}
8、查看 Laravel Telescope 中请求中的 SQL 语句。如图4
select count(*) as aggregate from `theme_asset` where `theme_id` = 'vogue1'




近期评论