# app\middleware\CsrfTokenCheck.php
<?php
namespace plugin\acms\app\middleware;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
class CsrfTokenCheck implements MiddlewareInterface
{
/**
* 排除的应用
* @var array
*/
protected $excludedApps = [];
/**
* 构造函数
*/
public function __construct($excludedApps = [])
{
$this->excludedApps = $excludedApps;
}
// 需要验证 CSRF 的 HTTP 方法
protected $methodsToVerify = ['POST', 'PUT', 'PATCH', 'DELETE'];
// 在 CsrfTokenCheck 中间件中添加:
public function process(Request $request, callable $handler): Response
{
// 当前请求的应用属于排除列表,则忽略
if (in_array($request->app, $this->excludedApps)) {
return $handler($request);
}
if (in_array($request->method(), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
$token = $request->header('X-CSRF-TOKEN') ?? '';
$sessionToken = $request->session()->get('csrf_token');
if (empty($token) || !hash_equals($sessionToken, $token)) {
return json(['code' => 419, 'msg' => 'CSRF token invalid'], 320);
}
}
return $handler($request);
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'CMS系统 - 内容管理系统')</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<link href="/app/user/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.2.1/css/all.min.css" rel="stylesheet">
<!-- Markdown解析库 -->
<link href="/app/acms/css/github-markdown.min.css" rel="stylesheet">
<link href="/app/acms/css/github.min.css" rel="stylesheet">
<link href="/app/acms/css/pagination.css" rel="stylesheet">
@yield('additional-styles')
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand fw-bold" href="/app/acms">CMS系统</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link {{ strpos(request()->uri(), '/app/acms') === 0 && request()->uri() === '/app/acms' ? 'active' : '' }}" href="/app/acms">首页</a>
</li>
@isset($categoryTree)
@foreach($categoryTree as $navCategory)
@if(isset($navCategory['children']) && count($navCategory['children']) > 0)
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {{ (isset($category) && $category->id == $navCategory['id']) || (isset($article) && $article->category_id == $navCategory['id']) ? 'active' : '' }}" href="/app/acms/list?category_id={{$navCategory['id']}}" id="navbarDropdown{{$navCategory['id']}}" role="button" data-toggle="dropdown" aria-expanded="false">
{{$navCategory['name']}}
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown{{$navCategory['id']}}">
@foreach($navCategory['children'] as $child)
<li><a class="dropdown-item" href="/app/acms/list?category_id={{$child['id']}}">{{$child['name']}}</a>
</li>
@endforeach
</ul>
</li>
@else
<li class="nav-item">
<a class="nav-link {{ (isset($category) && $category->id == $navCategory['id']) || (isset($article) && $article->category_id == $navCategory['id']) ? 'active' : '' }}" href="/app/acms/list?category_id={{$navCategory['id']}}">{{$navCategory['name']}}</a>
</li>
@endif
@endforeach
@endisset
</ul>
<form class="d-flex" action="/app/acms/list" method="GET">
<div class="input-group">
<input class="form-control" type="search" name="keyword" placeholder="搜索文章..." aria-label="搜索" value="{{ $params['keyword'] ?? '' }}">
<button class="btn btn-primary" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</form>
<div class="d-flex align-items-center ms-3">
@if(session('user'))
<div class="nav-item dropdown">
<a class="dropdown-toggle text-secondary" href="#" role="button" data-toggle="dropdown">
<img src="{{ session('user.avatar') }}" class="rounded me-2" height="40px" width="40px" />{{ session('user.nickname') }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/app/user">会员中心</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="/app/user/logout">退出</a></li>
</ul>
</div>
@else
<a href="/app/user/login" class="btn btn-primary me-2">登录</a>
@if(($setting['register_enable'] ?? true))
<a href="/app/user/register" class="btn btn-outline-primary">注册</a>
@endif
@endif
</div>
</div>
</div>
</nav>
<!-- 内容区域 -->
<div class="container my-4">
<div class="row">
<!-- 主内容区 -->
<div class="col-lg-8">
@yield('content')
</div>
<!-- 侧边栏 -->
<div class="col-lg-4">
@section('sidebar')
<!-- 分类列表 -->
@isset($categories)
<div class="card mb-4">
<div class="card-header bg-white">
<h5 class="mb-0">分类列表</h5>
</div>
<div class="card-body">
<ul class="list-unstyled">
@foreach($categories as $sidebarCategory)
<li class="sidebar-item">
<a href="/app/acms/list?category_id={{$sidebarCategory->id}}" class="text-decoration-none text-dark {{ (isset($category) && $category->id == $sidebarCategory->id) || (isset($article) && $article->category_id == $sidebarCategory->id) ? 'fw-bold text-primary' : '' }}">
<i class="fa fa-folder-open"></i>
{{$sidebarCategory->name}}
<span class="badge bg-light text-dark ms-2"><i class="fa fa-file-alt me-1"></i>{{$sidebarCategory->articles_count ?? 0}}</span>
</a>
</li>
@endforeach
</ul>
</div>
</div>
@endisset
<!-- 标签云 -->
@isset($tags)
<div class="card">
<div class="card-header bg-white">
<h5 class="mb-0">标签云</h5>
</div>
<div class="card-body">
<div class="tag-cloud">
@foreach($tags as $sidebarTag)
<a href="/app/acms/list?tag_id={{$sidebarTag->id}}" class="tag text-decoration-none">
<i class="fa fa-tag"></i> {{$sidebarTag->name}}
@if(isset($sidebarTag->articles_count))
<span style="font-size:0.8em;opacity:0.85;">({{$sidebarTag->articles_count}})</span>
@endif
</a>
@endforeach
</div>
</div>
</div>
@endisset
@show
</div>
</div>
</div>
<script src="/app/user/js/jquery.min.js"></script>
<script src="/app/user/js/bootstrap.bundle.min.js"></script>
<!-- Markdown解析库 -->
if (typeof jQuery !== 'undefined') {
$(document).ajaxSend(function(event, xhr, options) {
// 仅对 POST/PUT/PATCH/DELETE 方法添加 CSRF Token
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(options.type)) {
xhr.setRequestHeader('X-CSRF-TOKEN', $('meta[name="csrf-token"]').attr('content'));
}
});
}
</script>
@yield('scripts')
</body>
</html>
中间件配置:
<?php
use plugin\acms\app\middleware\CsrfTokenCheck;
use plugin\admin\api\Middleware as AdminMiddleware;
use plugin\user\api\Middleware as UserMiddleware;
return [
'' => [
new UserMiddleware(['admin']),
new CsrfTokenCheck(['admin']),
],
'admin' => [
AdminMiddleware::class
]
];

少了方法csrf_token()的示例
评论里面加了
webman官网论坛再次编辑有bug,
这种对传统模板页面,这样做没问题,如果是spa单页或前后端分离的项目,建议将csrf_token 写到cookie或header头部,然后每次请求自动带着走。我自己是产生csrf和校验csrf是分开的: