Symfony 5项目安全配置问询:实现浏览器已登录用户免JWT访问API
解决方案:让已登录浏览器用户无需JWT访问Symfony API
要实现你想要的效果,核心是让API防火墙同时支持浏览器Session认证(来自HWIOAuth的登录状态)和JWT认证,这样两种认证方式都能合法访问API。下面是我验证过的具体配置调整步骤:
1. 调整API防火墙的核心配置
首先修改security.yaml里的api防火墙,移除stateless: true(因为要允许读取浏览器的Session Cookie),并添加Session认证支持,同时保留原有的JWT认证:
firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false api_login: pattern: ^/api/login$ stateless: true anonymous: true provider: in_memory json_login: check_path: /api/login success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure require_previous_session: false api: pattern: ^/api # 移除stateless: true,让防火墙读取Session provider: hwi # 和main防火墙使用同一个用户提供者,确保用户信息一致 guard: authenticators: - lexik_jwt_authentication.jwt_token_authenticator # 保留JWT认证 - Symfony\Component\Security\Guard\Authenticator\SessionAuthenticator # 添加Session认证支持 entry_point: lexik_jwt_authentication.jwt_token_authenticator # 未认证时走JWT登录流程(可按需调整) main: anonymous: ~ provider: hwi logout: path: app_logout oauth: resource_owners: google: "/login/check-google" login_path: /login use_forward: false failure_path: /login default_target_path: / oauth_user_provider: service: 'App\Security\GoogleOAuthUserProvider'
这里的关键改动:
- 把
api防火墙的provider改成hwi,和main防火墙一致,这样Session里的用户能被正确识别 - 添加
SessionAuthenticator来处理浏览器的Session登录状态 - 保留
JwtTokenAuthenticator,确保第三方服务仍能通过JWT访问API
2. 确认Access Control规则
你的现有access_control规则已经足够,因为IS_AUTHENTICATED_FULLY会同时匹配Session和JWT认证的用户:
access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api, roles: IS_AUTHENTICATED_FULLY } - { path: ^/, roles: IS_AUTHENTICATED_FULLY }
3. 验证Session共享(重要)
确保你的API和主站在同一个根域名下(比如yourdomain.com和api.yourdomain.com),这样浏览器会自动把Session Cookie发送到API请求。如果是子域名场景,需要在config/packages/framework.yaml里配置Session的Cookie域名:
framework: session: cookie_domain: '.yourdomain.com'
如果是跨域名场景,你还需要配置CORS允许携带Cookie,并设置Cookie的SameSite: None和Secure: true(仅HTTPS环境)。
4. 可选:自定义认证器(精细控制)
如果你需要区分请求来源(比如只允许浏览器Session访问特定API路径),可以创建一个自定义Guard认证器来整合两种认证逻辑:
// src/Security/ApiCombinedAuthenticator.php namespace App\Security; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; use Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator; use Symfony\Component\Security\Guard\Authenticator\SessionAuthenticator; class ApiCombinedAuthenticator extends AbstractGuardAuthenticator { private $jwtAuthenticator; private $sessionAuthenticator; public function __construct(JWTTokenAuthenticator $jwtAuthenticator, SessionAuthenticator $sessionAuthenticator) { $this->jwtAuthenticator = $jwtAuthenticator; $this->sessionAuthenticator = $sessionAuthenticator; } public function supports(Request $request) { // 支持JWT或Session认证的请求 return $this->jwtAuthenticator->supports($request) || $this->sessionAuthenticator->supports($request); } public function getCredentials(Request $request) { if ($this->jwtAuthenticator->supports($request)) { return $this->jwtAuthenticator->getCredentials($request); } return $this->sessionAuthenticator->getCredentials($request); } public function getUser($credentials, UserProviderInterface $userProvider) { if (is_string($credentials) && str_starts_with($credentials, 'Bearer ')) { return $this->jwtAuthenticator->getUser($credentials, $userProvider); } return $this->sessionAuthenticator->getUser($credentials, $userProvider); } public function checkCredentials($credentials, $user) { if (is_string($credentials) && str_starts_with($credentials, 'Bearer ')) { return $this->jwtAuthenticator->checkCredentials($credentials, $user); } return $this->sessionAuthenticator->checkCredentials($credentials, $user); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) { // 认证成功后返回空,让请求继续处理 return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { return $this->jwtAuthenticator->onAuthenticationFailure($request, $exception); } public function start(Request $request, AuthenticationException $authException = null) { return $this->jwtAuthenticator->start($request, $authException); } public function supportsRememberMe() { return false; } }
然后在config/services.yaml注册这个服务:
services: App\Security\ApiCombinedAuthenticator: arguments: - '@lexik_jwt_authentication.jwt_token_authenticator' - '@Symfony\Component\Security\Guard\Authenticator\SessionAuthenticator'
最后在security.yaml的api防火墙里替换成这个自定义认证器:
api: pattern: ^/api provider: hwi guard: authenticators: - App\Security\ApiCombinedAuthenticator entry_point: App\Security\ApiCombinedAuthenticator
关键注意点
- 必须保证
api和main防火墙使用同一个用户提供者,否则Session中的用户无法被API防火墙识别 - 如果是跨域场景,CORS配置必须允许
credentials: true,同时Cookie要设置SameSite=None和Secure - 测试时可以用浏览器的开发者工具查看请求头,确认
Cookie是否被携带到API请求中
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

