Antes de ler este artigo, você provavelmente não ouviu falar do Shiro. Então, o que é Shiro? Shiro é um projeto de código aberto muito conhecido da Apache Foundation (site oficial: http://shiro.apache.org/). A introdução oficial é a seguinte:
Apache Shiro™ é um framework de segurança Java poderoso e fácil de usar, que executa autenticação, autorização, criptografia e gerenciamento de sessões. Com a API de fácil compreensão do Shiro, você pode proteger rapidamente qualquer aplicação — desde os menores aplicativos móveis até as maiores aplicações web e corporatvias.
Agora, vamos conhecer o Shiro de perto.
1. Criando um Projeto Maven
1.1 Selecionando o Template Maven e Preenchendo as Informações do Projeto
Selecione Maven e escolha o template quickstart.
Preencha as informações do projeto.
Configure o Maven do projeto; se não houver configurações especiais, basta avançar.
Defina o caminho de salvamento do projeto.
O projeto Maven está criado. Agora vamos criar o demo do Shiro.
2. Desenvolvendo o Demo do Shiro
2.1 Adicionando as Dependências
Adicione as seguintse dependências ao seu arquivo pom.xml:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.6</version>
</dependency>
2.2 Implementando o Código Java
package com.exemplo Shiro;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.
*/
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Aplicação demonstrando como utilizar a API do Shiro.
*/
public class DemonstracaoShiro {
private static final Logger logger = LoggerFactory.getLogger(DemonstracaoShiro.class);
public static void main(String[] args) {
// A maneira mais simples de criar um SecurityManager configurado
// é usar o arquivo de configuração INI
Factory<SecurityManager> fabrica = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager gerenciadorSeguranca = fabrica.getInstance();
// Disponibiliza o SecurityManager como um singleton JVM
SecurityUtils.setSecurityManager(gerenciadorSeguranca);
// Obtém o usuário atualmente em execução
Subject usuarioAtual = SecurityUtils.getSubject();
// Trabalhando com Session (não precisa de container web ou EJB!)
Session sessao = usuarioAtual.getSession();
sessao.setAttribute("chaveTeste", "valorExemplo");
String valorRecuperado = (String) sessao.getAttribute("chaveTeste");
if (valorRecuperado.equals("valorExemplo")) {
logger.info("Valor correto recuperado! [{}]", valorRecuperado);
}
// Realiza o login para verificar papéis e permissões
if (!usuarioAtual.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("usuario1", "senha123");
token.setRememberMe(true);
try {
usuarioAtual.login(token);
} catch (UnknownAccountException ex) {
logger.info("Usuário '{}' não encontrado", token.getPrincipal());
} catch (IncorrectCredentialsException ex) {
logger.info("Senha incorreta para '{}'", token.getPrincipal());
} catch (LockedAccountException ex) {
logger.info("Conta '{}' está bloqueada", token.getPrincipal());
} catch (AuthenticationException ex) {
logger.info("Erro de autenticação");
}
}
// Exibe informações do usuário logado
logger.info("Usuário [{}] autenticado com sucesso.", usuarioAtual.getPrincipal());
// Verifica papéis do usuário
if (usuarioAtual.hasRole("admin")) {
logger.info("Papel de administrador確認!");
} else {
logger.info("Acesso padrão.");
}
// Verifica permissões específicas
if (usuarioAtual.isPermitted("recurso:acao")) {
logger.info("Permissão para executar ação no recurso concedida.");
} else {
logger.info("Sem permissão para esta ação.");
}
// Verifica permissão em nível de instância
if (usuarioAtual.isPermitted("sistema:operacao:identificador1")) {
logger.info("Permissão para operar 'identificador1' concedida.");
} else {
logger.info("Acesso negado para 'identificador1'.");
}
// Finaliza a sessão
usuarioAtual.logout();
System.exit(0);
}
}
O código acima é uma adaptação do exemplo oficial do Shiro, simplificado para facilitar o entendimento.
2.3 Importando os Arquivos de Configuração
Arquivo de configuração de log, nome: log4j.properties:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
log4j.logger.org.apache=WARN
log4j.logger.org.springframework=WARN
log4j.logger.org.apache.shiro=INFO
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
Arquivo de configuração do Shiro, nome: shiro.ini:
#
# Licensed to the Apache Software Foundation (ASF)
#
# -----------------------------------------------------------------------------
# Usuários e seus papéis atribuídos
# -----------------------------------------------------------------------------
[users]
# Formato: usuario = senha, papel1, papel2
root = senha123, administrador
convidado =123456, visitante
usuario1 = senha123, usuario, operacao
admin = admin123, administrador, supervisor
operador = oper123, operador
# -----------------------------------------------------------------------------
# Papéis com permissões atribuídas
# -----------------------------------------------------------------------------
[roles]
# Administrador tem todas as permissões
administrador = *
# Papel operador pode acessar recursos específicos
operador = recurso:*
# Supervisor tem permissões administrativas
supervisor = sistema:*
3. Executando o Demo
Execute o método main da classe Java. É importante notar que o Shiro requer o SLF4J para logging. Adicione as seguintes dependências:
<!-- Configuração de logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.24</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.24</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.24</version>
</dependency>
Após a execução, você verá os seguintes logs no console:
2019-10-19 12:00:04,751 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Habilitando scheduler de validação de sessão
2019-10-19 12:00:05,508 INFO [com.exemploShiro.DemonstracaoShiro] - Valor correto recuperado! [valorExemplo]
2019-10-19 12:00:05,512 INFO [com.exemploShiro.DemonstracaoShiro] - Usuário [usuario1] autenticado com sucesso.
2019-10-19 12:00:05,512 INFO [com.exemploShiro.DemonstracaoShiro] - Papel de administrador確認!
2019-10-19 12:00:05,513 INFO [com.exemploShiro.DemonstracaoShiro] - Permissão para executar ação no recurso concedida.
2019-10-19 12:00:05,514 INFO [com.exemploShiro.DemonstracaoShiro] - Permissão para operar 'identificador1' concedida.
Nosso primeiro demo do Shiro está funcionando corretamente. Agora vamos analisar o código em detalhes.
4. Análise do Código
Vamos explorar os principais conceitos do Shiro demonstrados no código.
4.1 Arquivo de Configuração do Shiro
O arquivo shiro.ini define usuários e permissões de forma simples:
[users]
# Define usuários com formato: nome=senha,papeis
root = senha123, administrador
usuario1 = senha123, usuario, operacao
[roles]
# Define permissões para cada papel
administrador = *
usuario = recurso:*
operacao = sistema:*
4.2 Fluxo de Inicialização do Shiro
public static void main(String[] args) {
// 1. Criar fábrica do SecurityManager a partir do arquivo INI
Factory<SecurityManager> fabrica = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2. Obter instância do SecurityManager
SecurityManager gerenciadorSeguranca = fabrica.getInstance();
// 3. Configurar o SecurityManager globalmente
SecurityUtils.setSecurityManager(gerenciadorSeguranca);
// 4. Obter o Subject (representação do usuário atual)
Subject usuarioAtual = SecurityUtils.getSubject();
// 5. Trabalhar com sessão do Shiro
Session sessao = usuarioAtual.getSession();
sessao.setAttribute("dados", "valor");
// 6. Realizar autenticação
if (!usuarioAtual.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("usuario1", "senha123");
try {
usuarioAtual.login(token);
} catch (AuthenticationException ex) {
// Tratar erro de autenticação
}
}
// 7. Verificar autorizações
usuarioAtual.hasRole("administrador");
usuarioAtual.isPermitted("recurso:acao");
// 8. Encerrar sessão
usuarioAtual.logout();
}
4.3 Conceitos Fundamentais
Subject: Representa o usuário atual ou entidade executando a operação. É o ponto central de toda operação de segurança no Shiro.
SecurityManager: É o coração do Shiro. Gerencia todos os Subjects e coordena a autenticação, autorização e manipulação de sessões.
Session: O Shiro oferece seu próprio sistema de sessões que funciona em qualquer ambiente (Java SE, Java EE, aplicações web). Uma vantagem significativa é que, em projetos web, ele se integra automaticamente com a HttpSession, permitindo acesso transparente aos dados da sessão.
AuthenticationException: É a classe base para exceções de autenticação. As subclasses mais comuns incluem UnknownAccountException (usuário inexistente), IncorrectCredentialsException (senha incorreta) e LockedAccountException (conta bloqueada).
UsernamePasswordToken: Classe que encapsula as credenciais do usuário. O método setRememberMe(true) permite que o usuário permaneça autenticado em sessões futuras.
O Shiro gerencia três aspectos principais de segurança: autenticação (verificar idetnidade do usuário), autorização (verificar permissões de acesso) e gerenciamento de sessões (manter estado do usuário). Cada um desses aspectos pode ser configurado de forma independente através de diferentes realms, permitindo integração com diversas fontes de dados como bancos de dados, LDAP ou serviços web.