A integração de sistemas de login de terceiros é uma prática essencial para melhorar a experiência do usuário e facilitar o processo de registro em aplicações modernas. Ao adotar o protocolo OAuth 2.0, como no caso do provedor QQ, o sistema não armazena senhas externas, mas sim um identificador único (OpenID) e um token de acesso (AccessToken) que garantem a identidade do usuário de forma segura.
1. Fundamentos do Fluxo OAuth 2.0
O processo baseia-se na confiança entre o provedor de identidade (QQ), o usuário e a sua aplicação. O fluxo padrão consiste em redirecionar o usuário para o portal do QQ, onde ele autoriza o acesso. Após a autorização, o provedor devolve um código temporário para a sua URL de retorno (callback), que o backend utiliza para trocar por credenciais de acesso definitivas.
Antes de iniciar o desenvolvimento técnico, é obrigatório possuir um domínio registrado e validado junto ao QQ Connect, além de completar a verificação de identidade de desenvolvedor na plataforma.
2. Implementação no Frontend
Em aplicações onde o frontend e o backend estão separados, uma técnica comum é abrir uma janela pop-up centralizada para gerenciar a interação com o provedor de autenticação, evitando que o usuário perca o estado atual da aplicação principal.
/**
* Abre uma janela centralizada para o fluxo de autenticação externa
*/
function dispararLoginExterno(enderecoUrl, larguraJanela = 650, alturaJanela = 450) {
const eixoX = (window.screen.width - larguraJanela) / 2;
const eixoY = (window.screen.height - alturaJanela) / 2;
const configuracoes = `toolbar=no, location=no, directories=no, status=no, menubar=no,
scrollbars=yes, resizable=no, copyhistory=no,
width=${larguraJanela}, height=${alturaJanela},
top=${eioY}, left=${eixoX}`;
const janelaPopup = window.open(enderecoUrl, "AutenticacaoQQ", configuracoes);
if (window.focus && janelaPopup) {
janelaPopup.focus();
}
}
3. Lógica do Backend com Spring Boot
O backend desempenha dois papéis: gerar a URL de autorização inicial e processar o retorno do provedor para validar o usuário.
@RestController
@RequestMapping("/auth/provedor/qq")
public class ControleAutenticacaoQQ {
@Autowired
private ConfiguracaoOauth configuracoes;
private static final Logger logTecnico = LoggerFactory.getLogger(ControleAutenticacaoQQ.class);
/**
* Gera o endpoint de redirecionamento para o portal do QQ
*/
@GetMapping("/url-autorizacao")
public String obterUrlRedirecionamento() {
String stateAleatorio = UUID.randomUUID().toString().replace("-", "");
StringBuilder construtorUrl = new StringBuilder(configuracoes.getUriAutorizacao());
construtorUrl.append("?client_id=").append(configuracoes.getAppId())
.append("&state=").append(stateAleatorio)
.append("&redirect_uri=").append(configuracoes.getCallbackUrl())
.append("&response_type=code");
return construtorUrl.toString();
}
/**
* Processa o código recebido após o consentimento do usuário
*/
@GetMapping("/callback")
public String gerenciarRetorno(@RequestParam("code") String codigoAutorizacao) {
Map<String, String> parametrosToken = new HashMap<>();
parametrosToken.put("code", codigoAutorizacao);
parametrosToken.put("grant_type", "authorization_code");
parametrosToken.put("redirect_uri", configuracoes.getCallbackUrl());
parametrosToken.put("client_id", configuracoes.getAppId());
parametrosToken.put("client_secret", configuracoes.getAppKey());
// 1. Troca o código pelo Access Token
String respostaToken = HttpCliente.executarGet(configuracoes.getUriToken(), parametrosToken);
Map<String, String> mapaResposta = formatarResposta(respostaToken);
String tokenAcesso = mapaResposta.get("access_token");
// 2. Obtém o identificador único (OpenID)
String respostaOpenId = HttpCliente.executarGet(configuracoes.getUriOpenId() + "?access_token=" + tokenAcesso);
String jsonLimpo = respostaOpenId.substring(respostaOpenId.indexOf("{"), respostaOpenId.indexOf("}") + 1);
DadosIdentidadeQQ identidade = new Gson().fromJson(jsonLimpo, DadosIdentidadeQQ.class);
// 3. Recupera os dados de perfil do usuário
Map<String, String> parametrosPerfil = new HashMap<>();
parametrosPerfil.put("access_token", tokenAcesso);
parametrosPerfil.put("openid", identidade.getOpenid());
parametrosPerfil.put("oauth_consumer_key", configuracoes.getAppId());
String dadosUsuarioJson = HttpCliente.executarGet(configuracoes.getUriUserInfo(), parametrosPerfil);
PerfilUsuarioQQ perfil = new Gson().fromJson(dadosUsuarioJson, PerfilUsuarioQQ.class);
return integrarComSistemaSeguranca(perfil, identidade.getOpenid());
}
private Map<String, String> formatarResposta(String raw) {
Map<String, String> resultado = new HashMap<>();
String[] partes = raw.split("&");
for (String par : partes) {
String[] kv = par.split("=");
if (kv.length > 1) resultado.put(kv[0], kv[1]);
}
return resultado;
}
private String integrarComSistemaSeguranca(PerfilUsuarioQQ perfil, String uidExterno) {
try {
Subject sujeito = SecurityUtils.getSubject();
// Utiliza o apelido do QQ para login ou o OpenID como credencial única
UsernamePasswordToken credencial = new UsernamePasswordToken(perfil.getNickname(), uidExterno);
sujeito.login(credencial);
return "/dashboard.html";
} catch (Exception erro) {
logTecnico.error("Falha na autenticação via QQ: {}", erro.getMessage());
return "/erro-login.html";
}
}
}
4. Armazenamento e Sincronização
Ao receber o openID, o sistema deve verificar se este identificador já está vinculado a uma conta local no banco de dados. Caso seja um novo usuário, recomenda-se criar um registro persistindo o openID e as informações básicas de perfil (como nome e foto) para sessões futuras. Em arquiteturas desacopladas, após o sucesso do login, o backend geralmente emite um token JWT ou gerencia a sessão via cookies seguros para manter o estado de autenticação no cliente.