Implantação de WSS e Fluxo de Autenticação em Mini-Programa de Batalha entre Amigos

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:

  1. Acesse o painel de gerenciamento de certificados SSL.
  2. Escolha a opção de certificado gratuito e inicie a solicitação.
  3. Preencha os dados do domínio principal ou subdomínio desejado.
  4. Selecione o método de validação por DNS.
  5. Adicione o registro TXT indicado pelo provedor na zona DNS do seu domínio.
  6. 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:

  1. O cliente chama wx.login() para obter um code temporário.
  2. O code é enviado ao servidor de negócio.
  3. O servidor utiliza appid, appsecret e code para trocar por openid e session_key junto aos servidores do WeChat.
  4. O servidor gera um token próprio (chamado de sessão de terceira parte) com tamanho adequado, entropia segura e prazo de validade.
  5. Esse token é armazenado localmente no mini-programa.
  6. 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.

Tags: WeChat Mini Program WSS SuperSocket wx.login Session Management

Publicado em 6-25 03:43