如何实现两个Symfony5站点间跳转的跨站自动登录?
实现方案
这个需求完全可实现,你之前的方案失效有两个核心原因:一是明文URL传参无任何安全校验,任何人修改参数即可登录任意账号;二是Symfony 5.4的Security认证组件默认仅会拦截防火墙配置的指定检查路径的请求,普通GET请求带参数不会触发你写在UserAuthenticator里的认证逻辑。
推荐使用带签名的一次性短时效令牌方案实现,安全性高且完全符合Symfony的认证流程,具体步骤如下:
步骤1:配置跨站共享密钥
在两个站点的.env文件中配置完全一致的随机签名密钥,密钥长度建议32位以上,不要硬编码在业务代码中:
# 两个站点该值必须完全相同 CROSS_SITE_LOGIN_SECRET=这里替换为你自己生成的随机长字符串
步骤2:站点1生成安全跳转链接
不要直接在URL中明文传递用户姓名,在站点1的控制器中生成带签名、带过期时间的一次性令牌,再拼接触发跳转的链接:
// 站点1中生成跳转链接的示例代码 public function buildSsoRedirectUrl(User $selectedUser): string { $secret = $this->getParameter('env(CROSS_SITE_LOGIN_SECRET)'); // 令牌载荷,仅存必要信息 $payload = [ 'firstname' => $selectedUser->getFirstname(), 'lastname' => $selectedUser->getLastname(), 'exp' => time() + 30, // 令牌30秒过期,仅给跳转瞬间使用 'nonce' => bin2hex(random_bytes(8)) // 随机串用于防重放攻击 ]; // 用共享密钥对载荷生成签名,防止参数被篡改 $payloadJson = json_encode($payload); $sign = hash_hmac('sha256', $payloadJson, $secret); $token = base64_encode($payloadJson . '|' . $sign); return 'https://myWebsite2.com/sso-login?token=' . urlencode($token); }
前端模板中正常使用该生成的链接作为href属性值,保留target="_blank"即可。
步骤3:站点2实现SSO登录接口
不要在原有表单登录的UserAuthenticator中硬加判断,单独新增一个SSO专用的登录路由处理逻辑,避免影响正常登录流程:
- 首先新增路由配置:
# 站点2 config/routes.yaml sso_entry: path: /sso-login controller: App\Controller\SecurityController::ssoLogin
- 编写控制器中的登录处理逻辑:
// 站点2 src/Controller/SecurityController.php public function ssoLogin( Request $request, UserAuthenticatorInterface $authenticator, UserProviderInterface $userProvider, CacheInterface $cache ): Response { $token = $request->query->get('token'); if (!$token) throw new AccessDeniedHttpException('登录凭证缺失'); $secret = $this->getParameter('env(CROSS_SITE_LOGIN_SECRET)'); $decodedToken = base64_decode($token); if (!str_contains($decodedToken, '|')) throw new AccessDeniedHttpException('凭证格式非法'); // 拆分载荷和签名 [$payloadJson, $sign] = explode('|', $decodedToken, 2); // 校验签名,确认参数未被篡改 $expectedSign = hash_hmac('sha256', $payloadJson, $secret); if (!hash_equals($expectedSign, $sign)) throw new AccessDeniedHttpException('凭证校验失败'); $payload = json_decode($payloadJson, true); // 校验令牌是否过期 if ($payload['exp'] < time()) throw new AccessDeniedHttpException('登录凭证已过期'); // 可选:校验nonce防止重放,需要缓存组件支持 $nonceKey = 'sso_nonce_' . $payload['nonce']; if ($cache->has($nonceKey)) throw new AccessDeniedHttpException('凭证已被使用'); $cache->set($nonceKey, 1, 60); // 按你自己的业务逻辑查找对应用户,不存在可按需自动创建或抛错 $user = $userProvider->findOneBy([ 'firstname' => $payload['firstname'], 'lastname' => $payload['lastname'] ]); if (!$user) throw new AccessDeniedHttpException('对应用户不存在'); // 触发Symfony官方登录流程,写入安全会话 return $authenticator->authenticateUserAndHandleSuccess( $user, $request, $this->get(Security\Http\Authenticator\AuthenticatorInterface::class), // 替换为你项目中实际使用的认证器服务 'main' // 替换为你security.yaml中配置的对应防火墙名称 ); }
注意事项
- 令牌有效期不要设置过长,10-60秒足够覆盖点击跳转的网络耗时,降低令牌被盗用的风险
- 不要跳过签名校验步骤,否则和明文传参没有区别
- 如果两个站点属于同一根域名,可按需调整cookie作用域实现会话共享,但新标签页场景下使用上述一次性令牌方案已完全满足需求
- 如果你需要更高安全等级,可将nonce校验逻辑替换为数据库/Redis存储,彻底防止令牌重放
内容的提问来源于stack exchange,提问作者team.dev
相关产品推荐
相关产品推荐

