Ao abrir o app do Taobao, WeChat, TikTok e tantos outros no seu celular, você vai perceber que “login por QR code” já virou praticamente um recurso padrão em quase todos os aplicativos multi-plataforma (PC + mobile).

Para as pessoas usuárias, o login por QR code melhora muito a experiência: não é preciso digitar repetidamente usuário e senha no teclado; basta pegar o celular e fazer uma leitura rápida do código para concluir a autorização. Para empresas, essa tecnologia também é um importante meio de direcionar o tráfego do PC para o mobile, aumentando o DAU do app e o engajamento/recorrência.

Então, por trás desse aparentemente simples “um scan e um toque”, que princípios técnicos estão escondidos? Como front-end e back-end colaboram para isso?

I. Por que precisamos de login por QR code?

Antes de entrar nos detalhes técnicos, vamos esclarecer o contexto de negócio. O login tradicional com usuário e senha tem alguns pontos de dor:

  1. Alto custo de digitação: especialmente em dispositivos públicos ou desconhecidos, digitar uma senha longa é bastante inconveniente.
  2. Risco de segurança: em lan houses ou computadores públicos, digitar a senha pode expor a conta ao risco de keyloggers e outros malwares.
  3. Fragmentação entre dispositivos: a pessoa usuária já está logada no celular, mas no PC precisa refazer todo o processo de autenticação.

O login por QR code resolve muito bem esses problemas. A essência é: usar o app mobile (já autenticado) para autorizar o PC (ainda não autenticado).

II. O ciclo de vida central do login por QR code

Seja no WeChat, QQ ou em sistemas internos, a lógica subjacente é, no fundo, a mesma. Podemos abstrair o ciclo de vida inteiro em cinco etapas e, na implementação, refiná-lo em três fases centrais.

A experiência completa em cinco passos:

  1. Gerar o QR code: o PC exibe um QR code com validade.
  2. Ler/scanear: a pessoa usuária abre o app e lê o QR code.
  3. Confirmar login: o app pergunta “permitir login no PC?” e a pessoa confirma.
  4. PC faz polling/escuta: o PC percebe que a autorização foi concedida.
  5. Login concluído: o PC obtém a credencial (Token) e passa a acessar o sistema normalmente.

Em termos de arquitetura de back-end e desenho de fluxo, reduzimos para três passos técnicos: gerar QR code -> scanear -> confirmar login. A seguir, vamos destrinchar cada etapa em nível de código.


III. Desmembrando as etapas centrais e a implementação técnica

Para implementar login por QR code, precisamos de um middleware-chave: Redis. Como esse fluxo envolve vários terminais (navegador no PC e app no celular) compartilhando estado e se comunicando, o Redis — com alta performance de leitura/escrita em memória e mecanismo de expiração de chaves — é uma escolha ideal para tratar essa transição de estados.

Fase 1: Gerar QR code (construir um comprovante de autorização)

Quando a pessoa usuária clica em “login por QR code” no PC, a história começa.

1. O front-end solicita ao back-end a geração do comprovante

O front-end no PC envia uma requisição ao servidor pedindo o QR code de login.

2. O back-end gera um ID globalmente único (QR_Code_ID)

Ao receber a requisição, o back-end gera um ID globalmente único (em geral, um UUID). Esse ID é a “alma” do QR code e vai acompanhar todo o processo.

3. Armazenamento de estado no Redis

Com o QR_Code_ID em mãos, o back-end o salva no Redis. Aqui precisamos manter uma máquina de estados. O estado inicial é NEW (ainda não escaneado).

Para evitar que o QR code fique válido indefinidamente (criando brechas de segurança e vazamento de memória), é obrigatório definir um tempo de expiração (TTL) para a key — normalmente entre 1 e 5 minutos.

Exemplo de estrutura no Redis:

  • Key: qrcode:status:{QR_Code_ID}
  • Value: NEW (ou o valor do enum correspondente, como 0)
  • Expire: 300s

4. Resposta ao front-end e geração da imagem

O back-end devolve o QR_Code_ID ao PC.

Há duas abordagens para gerar a imagem do QR code:

  • Gerar no back-end: o back-end usa bibliotecas como ZXing para combinar QR_Code_ID com um URL Scheme e gerar a imagem do QR code, convertendo para Base64 e retornando ao front-end para renderização.
  • Gerar no front-end (recomendado): o back-end retorna apenas o QR_Code_ID e o link/protocolo de redirecionamento ao escanear (por exemplo, myapp://login/auth?qrcodeId=xxx). O front-end usa uma biblioteca JavaScript (como qrcode.js) para gerar a imagem diretamente no navegador. Isso reduz bastante o custo de CPU no servidor.

Fase 2: App escaneia (transição de estado e autorização temporária)

Depois que o QR code é exibido, a tela do PC mostra um código aguardando o scan. Nesse momento, o PC não pode simplesmente “esperar”; ele precisa saber se o código foi escaneado ou não.

1. O “aguardo ansioso” do PC (sincronização de estado)

O PC precisa consultar continuamente o back-end para confirmar o estado do QR code. Há três soluções comuns:

  • Short polling: o front-end usa setInterval e, a cada 1–2 segundos, envia uma requisição HTTP ao back-end. É simples de implementar, mas gera pressão alta no servidor por produzir muitas requisições inúteis (em alguns cenários o Taobao ainda usa polling).
  • WebSocket: PC e back-end estabelecem um canal bidirecional. Quando o estado muda, o back-end envia uma notificação ativa ao front-end. A latência é excelente, mas aumenta a complexidade arquitetural.
  • SSE (Server-Sent Events): uma tecnologia de conexão longa unidirecional, em que o back-end pode enviar dados ao front-end. É muito adequada para login por QR code, já que o caso é “back-end notificando o front-end”.

2. O app inicia o scan

A pessoa usuária abre o app no celular e, nesse momento, o app precisa estar em estado logado. O app chama a câmera do sistema, lê o QR code e extrai o QR_Code_ID.

3. O app envia ao back-end a requisição de scan

Com o QR_Code_ID em mãos, o app imediatamente envia ao back-end uma requisição dizendo “eu escaneei”.

Essa requisição é crucial e deve carregar dois itens:

  • O QR_Code_ID recém extraído.
  • A credencial de identidade do usuário no app (por exemplo, App_User_Token).

4. O back-end processa a lógica de scan

Ao receber a requisição, o back-end valida primeiro se o App_User_Token é válido. Se o token estiver expirado, a pessoa usuária será solicitada a fazer login no app.

Se passar na validação, o back-end identifica “qual usuário está escaneando” e, então, atualiza o Redis:

  • Atualiza o estado de qrcode:status:{QR_Code_ID} de NEW (não escaneado) para SCANNED (escaneado e aguardando confirmação).
  • Atenção: gerar um Token temporário (Temp_Token). O back-end gera um token temporário de uso único, associa ao QR_Code_ID e grava no Redis. Em seguida, retorna esse Temp_Token ao app.

Por que não logar diretamente? Por que adicionar um “token temporário”?

Isso existe por segurança e controle do usuário. Escanear não significa autorizar imediatamente: a pessoa pode ter escaneado sem querer, ou ter sido induzida a escanear. Portanto, após o scan, o app deve exibir uma tela de “confirmar login no PC”. O token temporário serve como verificação antifraude para a próxima etapa, garantindo que a confirmação veio do mesmo dispositivo que acabou de escanear.

Enquanto isso, o PC, via polling ou WebSocket, percebe que o estado no Redis mudou para SCANNED. A UI costuma reagir rapidamente: aplica uma máscara semitransparente sobre o QR code e exibe “Scan concluído. Confirme no celular para continuar”.


Fase 3: Confirmar login (emitir a credencial formal)

Agora a pressão está do lado do app: na tela, aparece o botão de “confirmar login no Web”.

1. O app envia a requisição de confirmação

Quando a pessoa usuária clica em “confirmar login”, o app leva o Temp_Token obtido no passo anterior e envia a requisição final de confirmação ao back-end.

2. O back-end faz validações e autoriza

O back-end recebe a requisição e executa a lógica mais central:

  • Validar o Temp_Token: consultar o Redis para conferir se o token temporário é válido e se corresponde ao QR_Code_ID da operação.
  • Evitar processamento duplicado: uma vez validado, remover imediatamente o Temp_Token do Redis (ou marcar como inválido), garantindo o uso único.
  • Atualizar estado para confirmado: mudar o estado qrcode:status:{QR_Code_ID} de SCANNED para VERIFIED (confirmado/usado).
  • Emitir o token do PC: como houve consentimento, o back-end gera um novo token PC_User_Token com as permissões adequadas.
  • Vincular informações do usuário: associar PC_User_Token à conta do usuário e gravar no Redis (ou no sistema de sessão).
  • Associar token ao QR code: para que o PC consiga obter o token, o back-end precisa armazenar temporariamente o PC_User_Token no Redis. A key pode continuar sendo o QR_Code_ID.

3. O PC obtém a “chave” e abre a porta

Na próxima consulta (ou via WebSocket), o PC finalmente recebe a boa notícia: o estado mudou para VERIFIED e a resposta inclui o PC_User_Token recém gerado.

O front-end do PC então salva o token em Cookie ou LocalStorage e redireciona para a home do sistema.

Com isso, o fluxo de login por QR code se fecha perfeitamente.


IV. Estrutura central de código (exemplo)

A seguir, um esqueleto de pseudocódigo do back-end mostrando a lógica de máquina de estados.

Enum: definição de estados do QR code

public enum QRCodeStateEnum {
    NEW(0, "待扫描"),
    SCANNED(1, "已扫描,待确认"),
    VERIFIED(2, "已确认登录"),
    EXPIRED(3, "二维码已过期");

    // ... 省略属性和构造方法
}

Endpoint de confirmação de login no back-end

@PostMapping("/qrcode/confirm")
public Result confirmLogin(@RequestParam("tempToken") String tempToken,
                           @RequestHeader("App-Token") String appToken) {

    // 1. 校验 App Token 是否有效
    UserInfo user = authService.verifyAppToken(appToken);
    if (user == null) {
        return Result.error(ErrorCodeEnum.LOGIN_FAIL, "App登录已失效,请重新登录");
    }

    // 2. 校验临时 Token,并解析出对应的二维码 ID
    String qrCodeId = redisService.get(RedisKey.TEMP_TOKEN_PREFIX + tempToken);
    if (StringUtils.isBlank(qrCodeId)) {
        return Result.error(ErrorCodeEnum.EXPIRED, "操作已过期,请重新扫码");
    }

    // 3. 防并发与幂等:删除临时 Token
    redisService.delete(RedisKey.TEMP_TOKEN_PREFIX + tempToken);

    // 4. 生成 PC 端的正式 Token
    String pcToken = jwtUtils.generateToken(user.getUserId());

    // 5. 更新二维码状态为已确认,并将 PC Token 放入 Redis 供前端轮询获取
    Map<String, Object> stateData = new HashMap<>();
    stateData.put("status", QRCodeStateEnum.VERIFIED.getCode());
    stateData.put("pcToken", pcToken);

    redisService.set(
        RedisKey.QR_CODE_STATUS_PREFIX + qrCodeId,
        stateData,
        60,
        TimeUnit.SECONDS
    );

    return Result.success("登录成功");
}

Endpoint de polling no PC

@GetMapping("/qrcode/status")
public Result checkStatus(@RequestParam("qrCodeId") String qrCodeId) {

    Object data = redisService.get(RedisKey.QR_CODE_STATUS_PREFIX + qrCodeId);
    if (data == null) {
        return Result.error(ErrorCodeEnum.QRCODE_EXPIRED, "二维码已失效");
    }

    // 解析 Redis 中的状态
    Map<String, Object> stateData = (Map<String, Object>) data;
    Integer status = (Integer) stateData.get("status");

    switch (status) {
        case 0: // NEW
            return Result.success(data).setMessage("请用手机扫码");
        case 1: // SCANNED
            return Result.success(data).setMessage("已扫描,请在手机端确认");
        case 2: // VERIFIED
            // 此时 data 中包含了 pcToken
            return Result.success(data).setMessage("登录成功");
        default:
            return Result.error(ErrorCodeEnum.SERVER_ERROR, "未知状态");
    }
}

V. Cenários de exceção e design de segurança

Em aplicações reais, há uma série de “cenários não ideais”:

1. Segurança: e se o QR code for sequestrado?

Toda a comunicação deve obrigatoriamente usar HTTPS para evitar ataques de man-in-the-middle. Além disso, o QR_Code_ID deve ser suficientemente aleatório (por exemplo, UUIDv4), para impedir enumeração ou tentativas de adivinhação por força bruta. No momento do scan no app, também é possível adicionar detecção de ambiente do dispositivo (por exemplo, alertas de risco por localização/IP). Se o IP do scan e o IP da requisição do PC estiverem em regiões muito distantes, pode-se exigir uma verificação adicional.

2. Por que separar “escaneado” e “confirmado” em dois estados?

Essa é uma aplicação clássica de defesa contra CSRF (Cross-Site Request Forgery). Se escanear já fosse suficiente para logar, uma pessoa mal-intencionada poderia disfarçar o seu QR code de login como uma imagem de “ganhe um brinde” e espalhar em um grupo. Se alguém escanear com o app sem saber, o PC do atacante entraria na conta. Ao introduzir o estado “aguardando confirmação”, o app força a pessoa usuária a ver uma mensagem explícita, como “confirmar login no WeChat para Windows”, devolvendo a decisão final de autorização para a pessoa.

3. Como desenhar o mecanismo de expiração do QR code?

Aproveitando o TTL do Redis. Ao gerar o QR_Code_ID, define-se um tempo de expiração de 3 minutos. Se o endpoint de polling no PC não encontrar a key, o código pode ser considerado expirado, e o front-end exibe uma máscara dizendo “QR code expirado, clique para atualizar”. Ao atualizar, o fluxo recomeça pela fase 1.