Como criar usuário no MySQL e dar permissões do jeito certo (8.4 e 9.x)

Para criar usuário no MySQL você usa dois comandos separados: CREATE USER cria a conta (nome, host de origem e senha) e GRANT dá as permissões. Parece simples, e é. O problema é que muita gente ainda segue tutorial de MySQL 5.x, faz tudo num GRANT ... IDENTIFIED BY, recebe erro de sintaxe e acaba resolvendo do pior jeito possível: GRANT ALL ON *.* para a aplicação. Vamos fazer direito.
Escrevo isto depois de mais de 40 anos em tecnologia, boa parte deles mexendo com MySQL, inclusive como embaixador do MySQL no Brasil. E se tem uma coisa que eu vejo repetida em ambiente de produção é a conta da aplicação com poder de DBA. Não é preguiça de ninguém. É falta de um roteiro claro. Então aqui vai o meu.
Qual é o comando para criar usuário no MySQL?
O padrão documentado no manual oficial do MySQL 8.4 é este:
CREATE USER 'app_loja'@'10.0.0.%' IDENTIFIED BY 'uma-senha-forte-aqui';
GRANT SELECT, INSERT, UPDATE, DELETE ON loja.* TO 'app_loja'@'10.0.0.%';
A seção Adding Accounts, Assigning Privileges, and Dropping Accounts do manual segue exatamente essa ordem: primeiro a conta, depois os privilégios. Desde o MySQL 8.0, o GRANT não cria mais conta implicitamente e não aceita mais IDENTIFIED BY. Se o seu script antigo faz isso, ele vai quebrar no 8.4 e no 9.x. Não é bug, é o comportamento correto.
Um detalhe que eu gosto de reforçar com quem está começando: não precisa rodar FLUSH PRIVILEGES depois de CREATE USER e GRANT. Esses comandos já atualizam as tabelas de permissão em memória. O FLUSH só faz sentido se alguém mexeu direto nas tabelas do schema mysql, coisa que você não deveria fazer.
O que significa o 'usuario'@'host'?
No MySQL, uma conta não é só um nome: é o par usuário + host de origem. 'app_loja'@'localhost' e 'app_loja'@'10.0.0.%' são duas contas diferentes, com senhas e permissões que podem ser diferentes.
'localhost': só conexões da própria máquina.'10.0.0.%': qualquer IP da faixa 10.0.0.x. O%funciona como curinga.'%': de qualquer lugar. Use com muita parcimônia.
Se você omitir o host, o manual do CREATE USER diz que ele assume '%'. Minha opinião: sempre escreva o host, mesmo quando for %. Deixa explícito que alguém pensou nisso.
Quais permissões dar para a aplicação?
Aqui está a parte que mais importa, e é onde eu vejo o maior estrago. O GRANT trabalha em níveis:
| Nível | Sintaxe | Quando usar |
|---|---|---|
| Global | ON *.* |
Contas de administração e ferramentas de monitoramento |
| Banco | ON loja.* |
O caso normal de uma aplicação |
| Tabela | ON loja.pedidos |
Integrações e relatórios que leem pouca coisa |
| Rotina | ON PROCEDURE loja.fechar_caixa |
Quando você expõe só procedures |
A regra que eu uso, de DBA para dev: a conta que a aplicação usa em produção precisa de SELECT, INSERT, UPDATE e DELETE no banco dela. E só. Ela não precisa de DROP, não precisa de ALTER, e definitivamente não precisa de nada em *.*.
"Mas o ORM roda migration na subida da aplicação." Então crie uma segunda conta, de migração, com CREATE, ALTER, INDEX, DROP e REFERENCES no banco, usada só no pipeline de deploy. Uma senha a mais para gerenciar custa muito menos do que um DROP TABLE vindo de uma falha de SQL injection na conta da aplicação.
Exemplos práticos:
-- conta de leitura para BI/relatórios
CREATE USER 'bi_leitura'@'10.0.5.%' IDENTIFIED BY 'outra-senha-forte';
GRANT SELECT ON loja.* TO 'bi_leitura'@'10.0.5.%';
-- conta de migração, usada só no deploy
CREATE USER 'migracao_loja'@'10.0.0.%' IDENTIFIED BY 'mais-uma-senha';
GRANT CREATE, ALTER, INDEX, DROP, REFERENCES, SELECT, INSERT, UPDATE, DELETE
ON loja.* TO 'migracao_loja'@'10.0.0.%';
Um ponto que pega muita gente: GRANT ALL não inclui GRANT OPTION. Isso está no manual. Então, se você vir WITH GRANT OPTION numa conta de aplicação, alguém colocou de propósito, e a conta pode distribuir os próprios privilégios para outras. Retire.
Como conferir e remover permissões?
Para ver o que uma conta pode fazer:
SHOW GRANTS FOR 'app_loja'@'10.0.0.%';
Para tirar uma permissão:
REVOKE DELETE ON loja.* FROM 'app_loja'@'10.0.0.%';
E para apagar a conta inteira:
DROP USER 'app_loja'@'10.0.0.%';
Se a ideia é só suspender o acesso (alguém saiu da equipe, um fornecedor encerrou o contrato), prefiro ALTER USER ... ACCOUNT LOCK. A conta continua lá, com o histórico de permissões, mas ninguém entra. Revisar periodicamente quem tem acesso a quê é um dos itens que eu olho primeiro num health check de MySQL.
Roles: vale a pena usar no MySQL?
Vale, e pouca gente usa. Desde o 8.0 o MySQL tem roles, que são conjuntos nomeados de privilégios. Em vez de repetir o mesmo GRANT para cada pessoa da equipe, você define o perfil uma vez. O exemplo da seção Using Roles do manual é praticamente o que eu recomendo:
CREATE ROLE 'app_read', 'app_write';
GRANT SELECT ON loja.* TO 'app_read';
GRANT INSERT, UPDATE, DELETE ON loja.* TO 'app_write';
CREATE USER 'ana'@'%' IDENTIFIED BY 'senha-da-ana';
GRANT 'app_read' TO 'ana'@'%';
SET DEFAULT ROLE ALL TO 'ana'@'%';
Repare no SET DEFAULT ROLE. O manual é claro: conceder uma role não a ativa automaticamente. Sem essa linha (ou sem a variável activate_all_roles_on_login ligada), o usuário conecta, tenta um SELECT e toma erro de acesso negado, e você perde meia hora achando que o GRANT não funcionou. Para ver os privilégios que vêm da role, use SHOW GRANTS FOR 'ana'@'%' USING 'app_read';.
Por que aparece erro de mysql_native_password?
Esta é provavelmente a dúvida que mais traz gente ao Google depois de uma atualização. O resumo, conferido na documentação oficial:
- Desde o MySQL 8.0, o plugin de autenticação padrão é o
caching_sha2_password. - No MySQL 8.4 LTS, o
mysql_native_passwordestá desabilitado por padrão. Dá para religar commysql_native_password=ONna seção[mysqld]do arquivo de configuração. A variáveldefault_authentication_pluginfoi removida no 8.4; quem controla isso agora éauthentication_policy. - No MySQL 9.0, segundo as notas de versão do 9.0.0, o plugin
mysql_native_passwordfoi removido do servidor, junto com a opção--mysql-native-password.
Na prática: se você criou usuários num 5.7 ou num 8.0 antigo com mysql_native_password e subiu para o 8.4, essas contas param de autenticar até você religar o plugin ou migrar as contas. No 9.x não há o que religar. A saída definitiva é migrar:
ALTER USER 'app_loja'@'10.0.0.%' IDENTIFIED WITH caching_sha2_password BY 'nova-senha';
E, junto com isso, conferir se o driver da sua aplicação suporta caching_sha2_password. Para achar as contas que ainda estão no plugin antigo:
SELECT user, host, plugin FROM mysql.user WHERE plugin = 'mysql_native_password';
Minha opinião, de CTO para CTO: não trate o mysql_native_password=ON como solução. Ele é uma ponte para você atravessar o upgrade, não um lugar para morar. Planeje a troca das contas antes de pensar em 9.x.
Que cuidados de segurança colocar já na criação?
O CREATE USER aceita várias opções que quase ninguém usa e que resolvem problemas reais. Todas estão no manual do 8.4:
CREATE USER 'fornecedor_x'@'203.0.113.10'
IDENTIFIED BY 'senha-temporaria'
REQUIRE SSL
PASSWORD EXPIRE INTERVAL 90 DAY
FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 2;
REQUIRE SSL: só aceita conexão criptografada. Para conta que vem de fora da sua rede, eu considero obrigatório.PASSWORD EXPIRE INTERVAL 90 DAY: força troca de senha periódica para contas de pessoas. Para conta de aplicação, pense bem antes, porque senha expirada às três da manhã derruba o sistema.FAILED_LOGIN_ATTEMPTScomPASSWORD_LOCK_TIME: bloqueia temporariamente a conta depois de tentativas erradas seguidas. Os dois valores precisam ser diferentes de zero para funcionar.IDENTIFIED BY RANDOM PASSWORD: o próprio servidor gera a senha e a devolve uma única vez. Ótimo para automação.CREATE USER IF NOT EXISTS: evita erro em scripts que rodam mais de uma vez.
Meu roteiro resumido
Se eu tivesse que caber tudo num post-it:
- Uma conta por aplicação, por ambiente. Nada de reaproveitar a mesma credencial em homologação e produção.
- Host restrito à faixa de rede de onde a aplicação realmente conecta.
- Só
SELECT,INSERT,UPDATE,DELETEno banco da aplicação. Migração em conta separada. - Pessoas recebem roles, não
GRANTavulso. rootnão sai dolocalhoste não vai para arquivo de configuração de aplicação.- Contas antigas em
mysql_native_passwordentram no plano de upgrade. - Revisão de
SHOW GRANTSpelo menos a cada trimestre.
Nada disso é sofisticado. É disciplina. E é exatamente o tipo de coisa que aparece num assessment de MySQL quando alguém finalmente olha com calma para o ambiente. Se você quer ir além, tenho mais conteúdo sobre o banco na página MySQL.
Perguntas frequentes
Preciso rodar FLUSH PRIVILEGES depois do GRANT?
Não. CREATE USER, GRANT, REVOKE e DROP USER já atualizam as permissões em memória. FLUSH PRIVILEGES só é necessário se alguém alterou as tabelas do schema mysql diretamente com INSERT ou UPDATE.
Por que o GRANT ... IDENTIFIED BY dá erro de sintaxe?
Porque essa forma foi removida no MySQL 8.0. Hoje a senha é definida no CREATE USER ou no ALTER USER, e o GRANT cuida apenas dos privilégios.
Como liberar acesso remoto a um usuário do MySQL?
Crie a conta com o host de onde a conexão vai partir, por exemplo 'app'@'10.0.0.%', e confira se o servidor está escutando na interface de rede (variável bind_address) e se o firewall libera a porta 3306. Evite '%' sempre que conseguir.
Como ver todos os usuários do MySQL?
Consulte SELECT user, host, plugin FROM mysql.user;. A coluna plugin já mostra quais contas ainda usam mysql_native_password.
Roles funcionam no MariaDB do mesmo jeito?
O MariaDB também tem roles, mas a sintaxe e o comportamento de ativação não são idênticos aos do MySQL. Consulte a documentação do MariaDB antes de reaproveitar scripts.
Quer dominar o MySQL de verdade?
Criar usuário e dar permissão é a porta de entrada. Segurança, desempenho, backup e alta disponibilidade vêm logo atrás. Escrevi o livro Aprendendo MySQL exatamente para quem quer entender o banco por dentro, sem decoreba. E, se você prefere aprender com laboratório e alguém tirando dúvida, conheça o curso MySQL: prática, teoria e laboratórios e os demais treinamentos. Se a dúvida é do seu ambiente específico, fale comigo pela página de contato.