Para mini-programas do WeChat, toda comunicação de rede deve utilizar protocolos seguros. Isso significa que tanto as requisições HTTP quanto os canais WebSocket precisam ser criptografados via HTTPS e WSS, respectivamente. Além disso, o domínio utilizado deve estar registrado e não pode conter número de porta na URL de produção.
Durante o desenvolvimento, as ferramentas oficiais do WeChat permitem desativar a verificação de certificados, mas na publicação o uso de WSS é obrigatório. O WSS (WebSocket Secure) é simplesmente o WebSocket transportado sobre TLS/SSL, garantindo confidencialidade e integridade dos dados trocados entre cliente e servidor.
Aquisição e Configuração do Certificado SSL
Antes de configurar o servidor WSS, é necessário obter um certificado digital para o domínio. Provedores como Tencent Cloud e Alibaba Cloud oferecem certificados gratuitos do tipo DV (Domain Validation).
O processo de obtenção segue os passos abaixo:
- Acesse o painel de gerenciamento de certificados SSL.
- Escolha a opção de certificado gratuito e inicie a solicitação.
- Preencha os dados do domínio principal ou subdomínio desejado.
- Selecione o método de validação por DNS.
- Adicione o registro TXT indicado pelo provedor na zona DNS do seu domínio.
- Aguarde a validação e faça o download do arquivo compactado.
Dentro do pacote baixado, utilize o certificado no formato compatível com IIS (arquivo .pfx). Ele será referenciado diretamente na aplicação servidor.
Servidor WSS com SuperSocket
Para o lado servidor, utilizaremos o SuperSocket, um framework open-source para aplicações de socket no .NET. Crie um projeto do tipo Console Application e instale os pacotes necessários via NuGet.
Após instalar os pacotes, copie o arquivo .pfx para a raiz do projeto e configure a propriedade Copiar para Diretório de Saída como Sempre Copiar. Isso garante que o certificado estará disponível na pasta de execução.
using Newtonsoft.Json.Linq;
using SuperSocket.SocketBase;
using SuperSocket.WebSocket;
using System;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
namespace WssBattleServer
{
class Program
{
static void Main(string[] args)
{
var certificado = new CertificateConfig
{
FilePath = "batalha.exemplo.com.pfx",
Password = "senha_do_certificado",
KeyStorageFlags = X509KeyStorageFlags.UserKeySet,
ClientCertificateRequired = false
};
var hostConfig = new ServerConfig
{
Security = "tls",
Certificate = certificado,
Ip = IPAddress.Any.ToString(),
Port = 2018
};
var servidor = new WebSocketServer();
servidor.NewSessionConnected += AoConectar;
servidor.NewMessageReceived += AoReceberMensagem;
servidor.NewDataReceived += AoReceberDados;
servidor.SessionClosed += AoDesconectar;
if (servidor.Setup(hostConfig))
{
servidor.Start();
Console.WriteLine("Servidor WSS iniciado na porta 2018");
Console.ReadKey();
}
}
static void AoConectar(WebSocketSession sessao)
{
sessao.Send("{\"tipo\":\"boas_vindas\",\"mensagem\":\"conexao estabelecida\"}");
}
static void AoDesconectar(WebSocketSession sessao, CloseReason motivo) { }
static void AoReceberDados(WebSocketSession sessao, byte[] dados) { }
static void AoReceberMensagem(WebSocketSession sessao, string mensagem)
{
// Processamento de autenticação descrito na seção seguinte
}
}
}
Em ambiente de produção no WeChat, a porta de escuta deve ser a 443, pois o mini-programa não aceita portas explícitas nas URLs de WSS.
Conexão do Mini-Programa ao Servidor WSS
No cliente, utilizamos a API wx.connectSocket para abrir o canal seguro. O retorno é um objeto SocketTask, que permite registrar os callbacks de abertura, mensagem, erro e fechamento.
let canal = wx.connectSocket({
url: 'wss://batalha.exemplo.com'
});
canal.onOpen(() => {
console.log('Canal WSS aberto');
});
canal.onMessage((evento) => {
const dados = JSON.parse(evento.data);
console.log('Mensagem recebida:', dados);
});
canal.onClose(() => {
console.log('Canal WSS fechado');
});
canal.onError((erro) => {
console.error('Erro na conexão WSS:', erro);
});
Sequência de Login do WeChat
O fluxo de autenticação de um mini-programa segue uma sequência bem definida para proteger a chave de sessão do usuário:
- O cliente chama
wx.login()para obter umcodetemporário. - O
codeé enviado ao servidor de negócio. - O servidor utiliza
appid,appsecretecodepara trocar poropenidesession_keyjunto aos servidores do WeChat. - O servidor gera um token próprio (chamado de sessão de terceira parte) com tamanho adequado, entropia segura e prazo de validade.
- Esse token é armazenado localmente no mini-programa.
- Nas requisições subsequentes, o token é validado no servidor antes de qualquer operação sensível.
A session_key nunca deve trafegar para o cliente. O token gerado pelo servidor funciona como uma referência opaca que mapeia para a sessão real.
Implementação do Login no Cliente
O código abaixo centraliza a lógica de autenticação no arquivo app.js, utilizando exclusivamente o canal WSS já aberto.
App({
onLaunch() {
this.iniciarSocket();
},
onShow() {
this.verificarAutenticacao();
},
iniciarSocket() {
this.socketTask = wx.connectSocket({
url: 'wss://batalha.exemplo.com'
});
this.socketTask.onOpen(() => {
console.log('Socket conectado');
});
this.socketTask.onMessage((resposta) => {
const payload = JSON.parse(resposta.data);
this.tratarResposta(payload);
});
},
tratarResposta(payload) {
if (payload.acao === 'validarToken') {
if (!payload.valido) this.autenticarUsuario();
else console.log('Usuário já autenticado');
}
if (payload.acao === 'autenticar') {
if (payload.sucesso) {
wx.setStorageSync('token_sessao', payload.token);
console.log('Login realizado com sucesso');
}
}
},
verificarAutenticacao() {
wx.checkSession({
complete: (resultado) => {
if (resultado.errMsg !== 'checkSession:ok') {
this.autenticarUsuario();
return;
}
const tokenArmazenado = wx.getStorageSync('token_sessao');
if (!tokenArmazenado) {
this.autenticarUsuario();
return;
}
this.socketTask.send({
data: JSON.stringify({
acao: 'validarToken',
token: tokenArmazenado
})
});
}
});
},
autenticarUsuario() {
wx.authorize({
scope: 'scope.userInfo',
complete: (autorizacao) => {
if (autorizacao.errMsg !== 'authorize:ok') {
wx.getSetting({
success: (configuracoes) => {
if (!configuracoes.authSetting['scope.userInfo']) {
wx.showModal({
title: 'Autorização necessária',
content: 'O jogo precisa acessar informações básicas do perfil para funcionar.',
success: (modal) => {
if (modal.confirm) wx.openSetting();
}
});
}
}
});
return;
}
wx.login({
success: (login) => {
if (login.errMsg === 'login:ok') {
this.socketTask.send({
data: JSON.stringify({
acao: 'autenticar',
code: login.code
})
});
}
}
});
}
});
}
});
Processamento das Ações no Servidor
No servidor, o método de recebimento de mensagens trata duas ações principais: validação do token existente e criação de uma nova sessão a partir do code enviado pelo cliente.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace WssBattleServer
{
class UserSession
{
public string OpenId { get; set; }
public string SessionKey { get; set; }
public string Token { get; set; }
public DateTime Expiracao { get; set; }
}
class Program
{
static readonly List<UserSession> sessoesAtivas = new List<UserSession>();
static void AoReceberMensagem(WebSocketSession sessao, string mensagem)
{
var payload = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(mensagem);
var acao = payload.Value<string>("acao");
switch (acao)
{
case "validarToken":
var tokenEnviado = payload.Value<string>("token");
var sessaoEncontrada = sessoesAtivas.Find(s => s.Token == tokenEnviado && s.Expiracao > DateTime.UtcNow);
sessao.Send(Newtonsoft.Json.JsonConvert.SerializeObject(new
{
acao = acao,
valido = sessaoEncontrada != null
}));
break;
case "autenticar":
var codigo = payload.Value<string>("code");
var credenciais = WeChatAuthHelper.TrocarCodePorSessao("seu_appid", "seu_appsecret", codigo);
var novaSessao = new UserSession
{
OpenId = credenciais.OpenId,
SessionKey = credenciais.SessionKey,
Token = GerarTokenSeguro(),
Expiracao = DateTime.UtcNow.AddHours(2)
};
sessoesAtivas.Add(novaSessao);
sessao.Send(Newtonsoft.Json.JsonConvert.SerializeObject(new
{
acao = acao,
sucesso = true,
token = novaSessao.Token
}));
break;
}
}
static string GerarTokenSeguro()
{
var bytes = new byte[32];
using (var gerador = RandomNumberGenerator.Create())
{
gerador.GetBytes(bytes);
}
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
}
}
A helper WeChatAuthHelper.TrocarCodePorSessao encapsula a chamada HTTPS para a API de login do WeChat (jscode2session), retornando o openid e a session_key. A lista sessoesAtivas representa o repositório em memória; em produção, substitua por um cache distribuído como Redis para suportar múltiplas instâncias do servidor.