代码之家  ›  专栏  ›  技术社区  ›  onizukaek

如何将无密码登录链接认证应用于无头架构?

  •  0
  • onizukaek  · 技术社区  · 1 年前

    我正在使用symfony后端实现一个api,为了进行身份验证,我使用内置库进行此处描述的无密码身份验证 https://symfony.com/doc/current/security/login_link.html#1-configure-the-login-link-authenticator

    我只实现了登录端点,login_check由库处理。

    登录所做的只是生成链接并将其发送到用户邮箱中,如下所示:

    <?php
    
    namespace App\Controller\Api;
    
    use App\Entity\User;
    use Psr\Log\LoggerInterface;
    use App\Repository\UserRepository;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\HttpFoundation\Exception\BadRequestException;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
    use Symfony\Component\Mailer\MailerInterface;
    use Symfony\Component\Mime\Email;
    use Symfony\Component\Routing\Annotation\Route;
    use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface;
    
    class AuthenticationController extends BaseController
    {
    
        public function __construct(
            LoggerInterface $logger,
            protected MailerInterface $mailer,
            protected EntityManagerInterface $entityManager
        ) {
            parent::__construct($logger);
        }
    
        #[Route(path: '/login', name: 'login', methods: ['POST'])]
        public function login(
            LoginLinkHandlerInterface $loginLinkHandler,
            UserRepository $userRepository,
            Request $request
        ): Response {
            // load the user in some way (e.g. using the form input)
            $email = $request->getPayload()->get('email');
    
            if (!$this->isValidEmail($email)) {
                throw new BadRequestHttpException("Email $email is not valid!");
            }
    
            $user = $userRepository->findOneBy(['email' => $email]);
    
            $emailTitle = "Your beligent.io Login link.";
            if (!$user) {
                $user = new User();
                $user->setEmail($email);
                $this->entityManager->persist($user);
                $this->entityManager->flush();
                $this->logger->info("User {$user->getEmail()} created!");
                $emailTitle = "Successfully registered to beligent.io, your login link.";
            }
            // create a login link for $user this returns an instance
            // of LoginLinkDetails
            $loginLinkDetails = $loginLinkHandler->createLoginLink($user);
            $loginLink = $loginLinkDetails->getUrl();
    
            $regex = '/^https?:\/\/[^\/]+(\/.*)$/';
    
            if (!preg_match($regex, $loginLink, $matches)) {
                throw new \Exception("Could not decode login link");
            }
    
            $feBaseURL = getenv("FRONTEND_URL");
            $customLoginLink = $feBaseURL . $matches[1];
    
            $email = (new Email())
                ->from('[email protected]')
                ->to($user->getEmail())
                ->subject($emailTitle)
                ->text('Myapp.io')
                ->html("<p>$customLoginLink</p>");
    
            $this->mailer->send($email);
    
            return new Response(null, Response::HTTP_NO_CONTENT);
        }
    
        private function isValidEmail(string $email)
        {
            $emailPattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
            return preg_match($emailPattern, $email);
        }
    }
    
    

    然后我在电子邮件中收到此链接:

    localhost:8888/[email protected]&expires=1718457559&hash=at3wXVy9CFjNZauoIs7Yds85pH4HBQplPUDznaEm1H0~D60nAF03Qti0aU2B2Z5nVMOl_evP1uYHUVXRtHzgea0~
    
    

    我的配置如下:

    security.yaml:

    security:
      # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
      password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: "auto"
      # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
      providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
          entity:
            class: App\Entity\User
            property: email
      firewalls:
        dev:
          pattern: ^/(_(profiler|wdt)|css|images|js)/
          security: false
        main:
          lazy: true
          provider: app_user_provider
          login_link:
            check_route: login_check
            signature_properties: ["id"]
            lifetime: 3600
            success_handler: App\Security\Authentication\AuthenticationSuccessHandler
    
          # activate different ways to authenticate
          # https://symfony.com/doc/current/security.html#the-firewall
    
          # https://symfony.com/doc/current/security/impersonating_user.html
          # switch_user: true
    
      # Easy way to control access for large sections of your site
      # Note: Only the *first* access control that matches will be used
      access_control:
        - { path: ^/login, roles: PUBLIC_ACCESS }
        - { path: ^/login_check, roles: PUBLIC_ACCESS }
        - { path: ^/doc, roles: PUBLIC_ACCESS }
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY }
    

    我还使用了nelmio_cors捆绑包,其配置如下:

    nelmio_cors.yaml

    nelmio_cors:
        defaults:
            origin_regex: true
            allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
            allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
            allow_headers: ['Content-Type', 'Authorization']
            allow_credentials: true
            expose_headers: ['Link']
            max_age: 3600
        paths:
            '^/': null
    

    和frameworks.yml:

    framework:
      secret: "%env(APP_SECRET)%"
      #csrf_protection: true
    
      # Note that the session will be started ONLY if you read or write from it.
      session:
        cookie_samesite: "none"
        cookie_secure: true
    
    

    当使用poster测试时,一切都很好,但当使用浏览器测试时,尽管服务器响应中包含Set cookie,但该浏览器不会存储PHPSESSID cookie。我使用chrome,在调试模式下打开“应用程序”选项卡时,我看不到存储任何cookie。

    以下是调用login_check时的服务器响应:

    HTTP/1.1 200 OK
    Server: nginx/1.22.1
    Content-Type: application/json
    Transfer-Encoding: chunked
    Connection: keep-alive
    X-Powered-By: PHP/8.2.20
    Cache-Control: max-age=0, must-revalidate, private
    Date: Sat, 15 Jun 2024 12:32:13 GMT
    Access-Control-Allow-Origin: http://localhost:3000
    Access-Control-Expose-Headers: link
    X-Robots-Tag: noindex
    Expires: Sat, 15 Jun 2024 12:32:13 GMT
    Set-Cookie: PHPSESSID=4f43da26e66330f7b41cf749a5306a93; path=/; httponly; samesite=lax
    

    收到了Set Cookie,但我不明白为什么没有存储此Cookie。 目前我可能错了,但我认为它可能来自于隐式的login_check端点,它可能会把头中的东西搞砸,因为对于单体架构来说,我知道如何在配置中更改它,但我不知道这背后有什么逻辑。

    基本上,问题是浏览器不存储会话cookie的最明显原因是什么?

    Symfony版本:7 PHP版本:8.2

    1 回复  |  直到 1 年前
        1
  •  0
  •   Dai    1 年前

    It was revealed in the comments-section OP正在制作 fetch 请求(间接地,通过Axios)没有 credentials: 'include' 明确规定, which means browsers will default to not including Cookies in any outgoing requests, nor persisting any new cookies in any Set-Cookie response headers .

    ……尽管令人失望的是,他们的浏览器没有警告他们这一事实。