Laravel 5 subscription middleware 如何避免重定向循环
支付小周
结论
重定向循环的原因是 billing 路由也经过了 subscription middleware。未订阅用户访问 billing 时,中间件会再次执行 redirect('billing'),请求因此反复重定向。
最稳妥的做法是:不要把该中间件应用到 billing 路由。如果暂时无法调整路由结构,也可以在 middleware 中明确放行 billing 请求。
推荐方案:将 billing 排除在 middleware 保护范围之外
只把需要有效订阅才能访问的页面放入路由组,billing 页面仅使用登录认证 middleware:
Route::group(['middleware' => 'auth'], function () {
Route::get('billing', 'BillingController@index');
Route::post('billing', 'BillingController@store');
Route::group(['middleware' => 'subscription'], function () {
Route::get('dashboard', 'DashboardController@index');
Route::get('reports', 'ReportController@index');
});
});
这样处理后:
- 未登录用户仍由
authmiddleware 处理。 - 已登录但没有有效订阅的用户可以访问 billing 页面。
- dashboard、reports 等受保护页面只有订阅有效时才能访问。
- billing 请求不会进入 subscription middleware,也就不会触发循环。
middleware 可以写得更直白一些:
public function handle($request, Closure $next)
{
if (!\Auth::check()) {
return $next($request);
}
$user = \Auth::user();
$hospital = $user->hospital;
$hasActiveSubscription = $hospital
&& $hospital->subscription === 'Active';
if (!$hasActiveSubscription) {
return redirect('billing');
}
return $next($request);
}
代码会先确认 $hospital 是否存在,避免用户未关联 hospital 时读取属性而引发异常。
备选方案:在 middleware 中放行 billing
如果该 middleware 已注册为全局 middleware,或者现有路由结构无法排除 billing,可以先判断当前请求是否指向 billing:
public function handle($request, Closure $next)
{
if ($request->is('billing') || $request->is('billing/*')) {
return $next($request);
}
if (\Auth::check()) {
$user = \Auth::user();
$hospital = $user->hospital;
$hasActiveSubscription = $hospital
&& $hospital->subscription === 'Active';
if (!$hasActiveSubscription) {
return redirect('billing');
}
}
return $next($request);
}
如果 billing 页面只有一个固定地址,可以简化为:
if ($request->is('billing')) {
return $next($request);
}
如果还要放行支付回调、更新付款方式等子路径,再加入 billing/*。
原条件中的问题
原来的判断是:
if (
\Auth::user()->hospital->subscription == null ||
\Auth::user()->hospital->subscription !== 'Active'
) {
return redirect('billing');
}
subscription !== 'Active' 已经包含 null 的情况,通常不必再单独判断:
if (\Auth::user()->hospital->subscription !== 'Active') {
return redirect('billing');
}
不过,需要先确认 subscription 字段保存的确实是字符串 'Active'。如果 subscription 是 Eloquent 关联,返回值就是订阅模型对象,不能直接与字符串比较,而应检查模型中的状态字段。例如:
$subscription = \Auth::user()->hospital->subscription;
if (!$subscription || $subscription->status !== 'Active') {
return redirect('billing');
}
应采用哪种写法,取决于 subscription 是数据库字段还是 Eloquent 关联。
注意事项
- billing 页面的表单提交路由也必须放行,否则页面虽然能打开,保存付款信息时仍会被重定向。
- 支付平台的 webhook 通常不依赖用户登录状态,也不应经过该 subscription middleware。
- 建议将 middleware 用作路由 middleware,不要注册成全局 middleware,这样更容易控制保护范围。
- 状态字符串必须与数据库中的实际值完全一致。使用严格比较时,
'Active'、'active'和'ACTIVE'是三个不同的值。 - 如果未登录用户不能继续访问,应交给
authmiddleware 统一处理,不要在 subscription middleware 中重复实现登录跳转。
备注:内容仅供参考。
