本技能聚焦 Symfony 项目中的认证(Authentication)与授权(Authorization),即“请求者是谁”和“这允许他们做什么”两个问题,并强调前者常被跳过。
【用途】 在应用开始有用户时即属于核心内容:安全接线检查、单一防火墙、用角色划分区域、用 Voter 判断对象级权限。加固表同样属于核心;登录之外的 #[RateLimit] 按需使用。
【安全是否真正接线】 新生成的项目默认并未接线:配方提供的防火墙没有 authenticator,且使用 users_in_memory provider——没有 User 实体、无法登录、#[CurrentUser] 也无从解析。可用以下命令检查: php bin/console debug:firewall main grep -A3 'providers:' config/packages/security.yaml 若 authenticator 列表为空且使用 users_in_memory,即为未改动的骨架状态。在编写端点前先检查:mine(#[CurrentUser] User $user) 会以“requires the $user argument that could not be resolved”报 500,且不会提示缺少 provider;而 ?User $user 会把崩溃变成静默的 null,更糟。
【三个必需部分】
- 实现 UserInterface 的实体(若有密码还需 PasswordAuthenticatedUserInterface);
- 指向该实体的 provider,并被防火墙引用;
- 防火墙上的 authenticator——form_login、json_login 或 access_token。provider 只加载用户,从不认证用户:只有 provider 而无 authenticator 的防火墙会静默地不让任何人进入。
【应避免的变通做法】
- 在请求负载中传 userId:这不是认证,调用方在自报身份,任何人都能发送别人的 id,且会向下游扩散,使每个服务都信任未认证的值。
- 仅为让 loginUser() 通过功能测试而添加实体 provider:loginUser() 直接把 token 写入 token storage,从不运行 authenticator,测试变绿而生产仍无法登录。 诚实做法是说明“这需要先有 User 实体和登录机制,涉及哪些内容”,而不是提供一个看似可用的端点。
【User 实体要点】 ROLE_USER 在 getter 中添加而非存储,确保任何行都不会缺失;getUserIdentifier() 必须稳定且唯一,它会进入会话和每个 token。eraseCredentials() 在 Symfony 8.0 中已从 UserInterface 移除(7.3 弃用);在 6.4–7.x 上实现为空。make:user 会为所安装版本生成正确结构。
【防火墙配置要点】 应用使用一个防火墙,外加 dev(属于排除而非边界)。多个防火墙意味着多个安全上下文,在一个中已认证的用户在另一个中是匿名的——这是常让人耗费一下午的 bug。唯一合理的第二个防火墙是用于独立路径前缀上 token API 的无状态防火墙:机制不同,且不得创建会话。password_hashers: auto 会选择平台提供的最佳算法,配合登录时的 needsRehash() 可在算法改进时迁移哈希;直接写 bcrypt 或 argon2id 会把决定冻结在无人再看的文件里。
【角色用于区域,Voter 用于对象】 “此人能否进入管理区、计费区?”用角色和 role_hierarchy;“此人能否编辑这条评论?”用 Voter。分界线在于答案是否取决于目标对象。控制器和模板命名动作而非角色:使用 #[IsGranted(ReviewVoter::EDIT, subject: 'review')],而非 ROLE_MODERATOR,使规则集中在一个类中。subject: 'review' 命名控制器参数,解析后的对象即 Voter 收到的对象。省略它会导致 Voter 收到 null,supports() 返回 false,所有 Voter 弃权,而全弃权决策默认被拒绝(allow_if_all_abstain 默认为 false),症状是本应允许的动作返回 403。
【属性与 access_control 的区别】 access_control 是网——每个 URL 区域一条规则,在控制器解析前应用,规则自上而下、仅首个匹配生效,故 ^/ 放最后。#[IsGranted] 是精确控制——此动作、此对象,且是两者中唯一能表达“作者”的。只有 #[IsGranted] 会在有人新增方法却忘记属性时漏掉;只有 access_control 完全无法表达所有权。
【Voter 实现要点】 第四个参数 ?Vote $vote = null 自 Symfony 7.3 起存在;在 6.4–7.2 上签名止于 TokenInterface $token,添加该参数会导致致命签名错误。编写前应阅读 vendor/symfony/security-core/Authorization/Voter/Voter.php。$vote?->addReason() 是 profiler 和 AccessDecision::getMessage() 显示的内容,把“Access Denied”变成一句话。角色检查应通过 AuthorizationCheckerInterface,而非 $token->getRoleNames():token 持有原始角色,role_hierarchy 由 checker 应用,直接读 token 会使 ROLE_ADMIN 不再隐含 ROLE_MODERATOR。
【加固措施】
- 防火墙上的 login_throttling:否则登录表单是在线密码猜测预言机,需要 symfony/rate-limiter;
- password_hashers: auto:否则算法被首个写文件的人冻结;
- 注册和改密 DTO 上的 #[Assert\PasswordStrength];
- #[Assert\NotCompromisedPassword]:否则撞库第一天就奏效;
- 登录之外的 #[RateLimit]:密码重置、注册、搜索和导出都可被滥用,login_throttling 均不覆盖。 #[RateLimit] 是 Symfony\Component\HttpKernel\Attribute\RateLimit,Symfony 8.1 新增,需要 symfony/rate-limiter 及 framework.rate_limiter 下声明的同名 limiter,单独使用会在请求时抛出“Rate limiter … does not exist”。8.1 之前应注入生成的 RateLimiterFactoryInterface 并显式消费。
【CSRF】 Form 组件会处理:每个表单都有 _token 字段并在提交时校验,无需编写代码。表单之外(POST 链接、fetch 调用、内联删除按钮)在动作的 #[Route] 旁加 #[IsCsrfTokenValid('delete-review', tokenKey: '_token')](Symfony 7.1+);更早版本在控制器中调用 isCsrfTokenValid()。JSON API 仅在通过 cookie 认证时才需要 CSRF,因为 CSRF 是为了对抗浏览器自动把 cookie 附加到跨站请求;读取 Authorization: Bearer … 的无状态 API 不受影响,浏览器不会自行添加该头。由自家前端调用的会话型 JSON 端点则确实需要。
【常见故障对照】
- #[CurrentUser] 无法解析 $user,或 ?User $user 始终为 null:无用户 provider,或路由不在已认证防火墙之后,检查 debug:firewall;
- Full authentication is required:access_control 匹配了匿名请求无法满足的规则,常见于 ^/ 放在 ^/login 之上;
- Voter 本应授予却 Access Denied:#[IsGranted] 缺少 subject:,Voter 收到 null,supports() 返回否;
- Voter 从未被调用:supports() 返回 false,属性字符串错误或 subject 类不符;
- 本应公开的端点返回 401:需要在兜底规则之上显式添加 PUBLIC_ACCESS 规则;
- 有 token 的表单报 Invalid CSRF token:无状态 CSRF(7.2 起默认)需要其 Stimulus 控制器在浏览器中加载;
- ROLE_ADMIN 不再隐含其下角色:角色从 token 读取而非通过授权检查器;
- 测试用户能登录但应用 403:loginUser() 绕过 authenticator,角色来自实体而非防火墙。
【参考文件】
- references/api-authentication.md:token 认证 API:access_token、数据库中可撤销的不透明 token、handler 及其测试;
- references/voters-and-roles.md:编写或单元测试 Voter、设计 role_hierarchy、access_control 顺序、Twig 中的 is_granted;
- references/hardening.md:限流、密码约束、速率限制、CSRF 细节、程序化登录、登出与模拟。