本文目录导读:

- 方法一:手动白名单数组(推荐)
- 方法二:使用 Laravel 查询作用域(封装)
- 方法三:使用第三方扩展包
spatie/laravel-query-builder - 方法四:使用 Laravel 8+ 的模型
sortableTrait(简洁版) - ⚠️ 需要特别防御的情况
- 总结建议
在 Laravel 中,排序字段默认是没有白名单机制的,直接通过用户输入进行排序存在 SQL 注入风险(虽然 Laravel 的查询构建器有参数绑定,但表名、列名无法被绑定)。
如果你允许用户传递 sort 参数,必须手动实现白名单校验,以下是几种推荐的安全实现方式:
手动白名单数组(推荐)
这是最安全、最常用的方式,明确指定允许排序的字段列表。
use Illuminate\Http\Request;
public function index(Request $request)
{
// 定义白名单
$allowedSorts = ['id', 'name', 'created_at', 'price'];
// 获取用户输入,默认按 id 降序
$sortField = $request->input('sort', 'id');
$sortDirection = $request->input('direction', 'desc');
// 检查是否在白名单内
if (!in_array($sortField, $allowedSorts)) {
$sortField = 'id'; // 或返回错误
}
// 检查排序方向(只允许 asc 或 desc)
$sortDirection = in_array($sortDirection, ['asc', 'desc']) ? $sortDirection : 'desc';
$users = User::orderBy($sortField, $sortDirection)->get();
return response()->json($users);
}
使用 Laravel 查询作用域(封装)
将排序逻辑封装在模型中,便于复用。
// 在 User 模型中定义 scope
public function scopeSortByWhiteList($query, $sortField, $sortDirection = 'desc')
{
$allowedSorts = ['id', 'name', 'email', 'created_at'];
if (!in_array($sortField, $allowedSorts)) {
$sortField = 'id';
}
$sortDirection = in_array($sortDirection, ['asc', 'desc']) ? $sortDirection : 'desc';
return $query->orderBy($sortField, $sortDirection);
}
// 在控制器中使用
$users = User::sortByWhiteList($request->sort, $request->direction)->get();
使用第三方扩展包 spatie/laravel-query-builder
这是最专业、最完善的解决方案,自带白名单、多字段排序、过滤等功能。
安装:
composer require spatie/laravel-query-builder
使用:
use Spatie\QueryBuilder\QueryBuilder;
public function index()
{
$users = QueryBuilder::for(User::class)
->allowedSorts(['name', 'email', 'created_at']) // 白名单
->defaultSort('created_at') // 默认排序
->get();
return response()->json($users);
}
前端传参方式:
GET /users?sort=name // 正序
GET /users?sort=-name // 降序(带 -)
GET /users?sort=name,-email // 多字段排序
使用 Laravel 8+ 的模型 sortable Trait(简洁版)
如果项目简单,可以自己写一个 Trait:
trait HasSortableFields
{
public function scopeSortable($query, $field, $direction = 'desc')
{
$allowedSorts = property_exists($this, 'sortable') ? $this->sortable : ['id'];
if (!in_array($field, $allowedSorts)) {
return $query;
}
$direction = in_array($direction, ['asc', 'desc']) ? $direction : 'desc';
return $query->orderBy($field, $direction);
}
}
// 在模型中使用
class User extends Model
{
use HasSortableFields;
protected $sortable = ['id', 'name', 'email', 'created_at'];
}
// 控制器
$users = User::sortable($request->sort, $request->direction)->get();
⚠️ 需要特别防御的情况
不要对以下类型的参数使用字符串拼接或 orderByRaw:
// ❌ 危险:直接拼接用户输入 $query->orderBy($request->sort); // 即使 $request->sort 是字符串,也可能被注入别名 // ✅ 安全:使用白名单 $query->orderBy($allowedField, $direction);
总结建议
- 简单项目:手写白名单数组(方法一)
- 中等项目:使用 Trait(方法四)
- 复杂项目/API 接口:使用
spatie/laravel-query-builder(方法三) - 这是目前社区最推荐的做法。
核心原则:永远不要让用户直接控制字段名作为 orderBy 的参数,除非你先对字段名做了白名单校验。