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:
- Alto custo de digitação: especialmente em dispositivos públicos ou desconhecidos, digitar uma senha longa é bastante inconveniente.
- 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.
- 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:
- Gerar o QR code: o PC exibe um QR code com validade.
- Ler/scanear: a pessoa usuária abre o app e lê o QR code.
- Confirmar login: o app pergunta “permitir login no PC?” e a pessoa confirma.
- PC faz polling/escuta: o PC percebe que a autorização foi concedida.
- 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_IDcom 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_IDe o link/protocolo de redirecionamento ao escanear (por exemplo,myapp://login/auth?qrcodeId=xxx). O front-end usa uma biblioteca JavaScript (comoqrcode.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
setIntervale, 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_IDrecé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}deNEW(não escaneado) paraSCANNED(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_IDe grava no Redis. Em seguida, retorna esseTemp_Tokenao 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_IDda operação. - Evitar processamento duplicado: uma vez validado, remover imediatamente o
Temp_Tokendo Redis (ou marcar como inválido), garantindo o uso único. - Atualizar estado para confirmado: mudar o estado
qrcode:status:{QR_Code_ID}deSCANNEDparaVERIFIED(confirmado/usado). - Emitir o token do PC: como houve consentimento, o back-end gera um novo token
PC_User_Tokencom 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_Tokenno Redis. A key pode continuar sendo oQR_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.