| name | webman-tech-auth-best-practices |
| description | webman-tech/auth 最佳实践。使用场景:用户配置认证授权时,给出明确的推荐写法。 |
webman-tech/auth 最佳实践
核心原则
- Guard 是认证上下文:一个 guard 对应一套用户体系(web/api/admin 等)
- 三件套:每个 guard 必须配置
identityRepository(查用户)、authenticationMethod(取凭证)、authenticationFailureHandler(失败处理)
- Web 用 Redirect,API 用 ThrowException
实现用户模型
两个接口职责不同,可以分开实现,也可以合并到同一个类:
IdentityInterface — 代表已认证的用户,实现 getId() 和 refreshIdentity()
IdentityRepositoryInterface — 负责根据 token/id 查找用户,实现 findIdentity()
合并实现(简单场景,常见写法)
class User extends Model implements IdentityInterface, IdentityRepositoryInterface
{
public function getId(): ?string
{
return (string) $this->id;
}
public function refreshIdentity(): static
{
return $this->fresh() ?? $this;
}
public function findIdentity(string $token, ?string $type = null): ?IdentityInterface
{
return static::where('access_token', $token)->first();
}
}
分开实现(复杂场景)
当查找逻辑较复杂,或需要注入依赖时,可以分开:
class User extends Model implements IdentityInterface
{
public function getId(): ?string { return (string) $this->id; }
public function refreshIdentity(): static { return $this->fresh() ?? $this; }
}
class UserRepository implements IdentityRepositoryInterface
{
public function findIdentity(string $token, ?string $type = null): ?IdentityInterface
{
return User::where('access_token', $token)->where('status', 1)->first();
}
}
'identityRepository' => fn() => new UserRepository(),
配置 Guard
use WebmanTech\Auth\Authentication\FailureHandler\RedirectHandler;
use WebmanTech\Auth\Authentication\FailureHandler\ThrowExceptionHandler;
use WebmanTech\Auth\Authentication\Method\SessionMethod;
use WebmanTech\Auth\Authentication\Method\HttpBearerMethod;
use WebmanTech\Auth\Interfaces\IdentityRepositoryInterface;
return [
'default' => 'web',
'guards' => [
'web' => [
'identityRepository' => fn() => new app\model\User(),
'authenticationMethod' => fn(IdentityRepositoryInterface $repo) => new SessionMethod($repo),
'authenticationFailureHandler' => fn() => new RedirectHandler(route('login')),
],
'api' => [
'identityRepository' => fn() => new app\model\User(),
'authenticationMethod' => fn(IdentityRepositoryInterface $repo) => new HttpBearerMethod($repo),
'authenticationFailureHandler' => fn() => new ThrowExceptionHandler(),
],
],
];
认证方式选择
| 场景 | 推荐方式 |
|---|
| 传统 Web 后台 | SessionMethod |
| REST API | HttpBearerMethod(Authorization: Bearer <token>) |
| 自定义 Header | HttpHeaderMethod(默认 X-Api-Key) |
| Query/Post 参数 | RequestMethod(默认 access-token) |
| JWT(tinywan/jwt) | TinywanJwtMethod |
| 多种方式并存 | CompositeMethod |
CompositeMethod:同时支持多种认证方式
use WebmanTech\Auth\Authentication\Method\CompositeMethod;
'authenticationMethod' => fn(IdentityRepositoryInterface $repo) => new CompositeMethod([
new SessionMethod($repo),
new HttpBearerMethod($repo),
]),
失败处理选择
| 场景 | 推荐处理器 |
|---|
| Web 页面 | RedirectHandler('/login') — 跳转登录页 |
| REST API | ThrowExceptionHandler — 抛出 UnauthorizedException,交给框架统一处理 |
| 简单 API | ResponseHandler — 直接返回 401 |
中间件配置
单 guard 场景
use WebmanTech\Auth\Middleware\Authentication;
return [
'' => [Authentication::class],
];
多 guard 场景:用 SetAuthGuard 切换
webman 中间件不支持带参数构造,需要为每个 guard 创建无参数子类:
class SetAuthGuardAdmin extends SetAuthGuard
{
public function __construct()
{
parent::__construct('admin');
}
}
路由配置时注意:登录接口只挂 SetAuthGuard,不挂 Authentication:
Route::group('/admin', function () {
Route::post('/auth/login', [AdminAuthController::class, 'login']);
})->middleware([SetAuthGuardAdmin::class]);
Route::group('/admin', function () {
Route::get('/auth/info', [AdminAuthController::class, 'info']);
})->middleware([SetAuthGuardAdmin::class, Authentication::class]);
完整的多用户体系示例(含 AuthController 写法)详见 references/multi-user-example.md。
Authentication 中间件会自动读取 SetAuthGuard 设置的 guard 名称。
控制器中获取用户
use WebmanTech\Auth\Auth;
$user = Auth::guard()->getUser();
$userId = Auth::guard()->getId();
$isGuest = Auth::guard()->isGuest();
$adminUser = Auth::guard('admin')->getUser();
登录 / 退出
use WebmanTech\Auth\Auth;
$user = User::where('email', $email)->first();
if ($user && password_verify($password, $user->password)) {
Auth::guard()->login($user);
}
Auth::guard()->logout();
扩展 Authentication 中间件
可选路由(登录/未登录都能访问)
use WebmanTech\Auth\Middleware\Authentication;
class OptionalAuthentication extends Authentication
{
protected function optionalRoutes(): array
{
return ['/api/public', 'route.name'];
}
}
额外用户检查(如封禁检测)
use WebmanTech\Auth\Middleware\Authentication;
use WebmanTech\Auth\Interfaces\IdentityInterface;
use WebmanTech\CommonUtils\Response;
class AppAuthentication extends Authentication
{
protected function checkIdentity(IdentityInterface $identity): ?Response
{
if ($identity->is_banned) {
return Response::make()->withStatus(403)->withBody('账号已封禁');
}
return null;
}
}
常见错误
| 错误 | 原因 | 解决 |
|---|
获取当前 guard 失败 | 没有当前 Request 上下文 | 确保在 HTTP 请求生命周期内调用 Auth::guard() |
not exist in auth.guards | guard 名称不在配置中 | 检查 auth.guards 配置键名 |
认证通过但 getUser() 返回 null | findIdentity 返回了 null | 检查 identityRepository 的查询逻辑 |
| Session 认证刷新后丢失登录状态 | 未使用 SessionMethod | 改用 SessionMethod,它会自动处理 session 读写 |
| API 认证失败返回 302 而非 401 | 用了 RedirectHandler | API guard 改用 ThrowExceptionHandler 或 ResponseHandler |