Objetivo:
Este artigo aborda a implementação de autorização utilizando o framework Apache Shiro e desenvolvimento baseado em anotações para controle de acesso em aplicações web.
Autorização com Shiro
Primeiramente, projetamos a estrutura de tabelas para o sistema de permissões do Shiro:
Camada de Mapper
A interface ShiroUserMapper define os métodos para rceuperar papéis e permissões do usuário:
Set<String> getPapeisPorIdUsuario(Integer uid);
Set<String> getPermissoesPorIdUsuario(Integer uid);
O arquivo de mapeamento XML correspondente:
<select id="getPapeisPorIdUsuario" resultType="java.lang.String" parameterType="java.lang.Integer">
SELECT r.papelid FROM t_shiro_usuario u, t_shiro_usuario_papel up, t_shiro_papel r
WHERE u.usuarioid = up.usuarioid AND up.papelid = r.papelid
AND u.usuarioid = #{uid}
</select>
<select id="getPermissoesPorIdUsuario" resultType="java.lang.String" parameterType="java.lang.Integer">
SELECT p.permissao FROM t_shiro_usuario u, t_shiro_usuario_papel up,
t_shiro_papel_permissoes pp, t_shiro_permissao p
WHERE u.usuarioid = up.usuarioid AND up.papelid = pp.papelid AND pp.permissaoeid = p.permissaoeid
AND u.usuarioid = #{uid}
</select>
Camada de Serviço
A interface do serviço:
/**
* Verificação de papéis do usuário
* @param uid
* @return
*/
Set<String> getPapeisPorIdUsuario(Integer uid);
/**
* Verificação de permissões
* @param uid
* @return
*/
Set<String> getPermissoesPorIdUsuario(Integer uid);
A implementação do serviço:
@Service("servicoShiro")
public class ServicoShiroImpl implements ServicoShiro {
@Autowired
private MapperShiro mapperShiro;
@Override
public UsuarioShiro buscarPorNome(String nomeUsuario) {
return mapperShiro.buscarPorNome(nomeUsuario);
}
@Override
public int inserir(UsuarioShiro usuarioShiro) {
return mapperShiro.inserir(usuarioShiro);
}
@Override
public Set<String> getPapeisPorIdUsuario(Integer uid) {
return mapperShiro.getPapeisPorIdUsuario(uid);
}
@Override
public Set<String> getPermissoesPorIdUsuario(Integer uid) {
return mapperShiro.getPermissoesPorIdUsuario(uid);
}
}
Implementação do Realm para Autorização
A classe customizada estendida de AuthorizingRealm:
public class MeuRealm extends AuthorizingRealm {
private ServicoShiro servicoShiro;
public ServicoShiro getServicoShiro() {
return servicoShiro;
}
public void setServicoShiro(ServicoShiro servicoShiro) {
this.servicoShiro = servicoShiro;
}
/**
* Método de autorização
* @param colecaoPrincipais
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection colecaoPrincipais) {
UsuarioShiro usuarioShiro = this.servicoShiro.buscarPorNome(colecaoPrincipais.getPrimaryPrincipal().toString());
Set<String>> idsPapeis = this.servicoShiro.getPapeisPorIdUsuario(usuarioShiro.getUsuarioid());
Set<String> idsPermissoes = this.servicoShiro.getPermissoesPorIdUsuario(usuarioShiro.getUsuarioid());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(idsPapeis);
info.setStringPermissions(idsPermissoes);
return info;
}
}
Desenvolvimento Baseado em Anotações com Shiro
Introdução às Anotações Comuns
- @RequiresAuthenticated: Indica que o Subject atual passou pela autenticação via login; ou seja, Subject.isAuthenticated() retorna true
- @RequiresUser: Indica que o Subject atual foi autenticado ou entrou usando o recurso "lembrar-me"
- @RequiresGuest: Indica que o Subject atual não foi autenticado ou não usou "lembrar-me", ou seja, está como visitante
- @RequiresRoles(value = {"admin","user"}, logical = Logical.AND): Requer que o Subject tenha os papéis "admin" e "user" simultaneamente
- @RequiresPermissions(value = {"usuario:excluir","usuario:editar"}, logical = Logical.OR): Requer que o Subject tenha a permissão "usuario:excluir" ou "usuario:editar"
É crucial habilitar as anotações do Shiro no arquivo spring-mvc.xml (no container Spring MVC), caso contrário, as anotações não funcionarão!
Configuração no Spring MVC
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"></property>
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">
naoAutorizado
</prop>
</props>
</property>
<property name="defaultErrorView" value="naoAutorizado"/>
</bean>
Utilização em Controllers
@Controller
public class ControladorUsuarioShiro {
@Autowired
private ServicoShiro servicoShiro;
/**
* Método com anotação de autenticação
* @param req
* @param resp
* @return
*/
@RequiresUser
@RequestMapping("/acessarUsuario")
public String acessarUsuario(HttpServletRequest req, HttpServletResponse resp){
return "admin/adicionarUsuario";
}
/**
* Método com anotação de verificação de papel
* Este método só pode ser acessado por usuários com os papéis 1 e 4 simultaneamente
* @param req
* @param resp
* @return
*/
@RequiresRoles(value = {"1","4"}, logical = Logical.AND)
@RequestMapping("/acessarPapel")
public String acessarPapel(HttpServletRequest req, HttpServletResponse resp){
return "admin/listarUsuarios";
}
/**
* Método para tratamento de falhas na autorização
* @param req
* @param resp
* @return
*/
@RequestMapping("/naoAutorizado")
public String tratarErro(HttpServletRequest req, HttpServletResponse resp){
System.out.print("Tratando erro de autorização!!!");
return "login";
}
/**
* Método com anotação de verificação de permissão
*
* @param req
* @param resp
* @return
*/
@RequiresPermissions(value = {"usuario:atualizar","usuario:visualizar"}, logical = Logical.OR)
@RequestMapping("/acessarPermissao")
public String acessarPermissao(HttpServletRequest req, HttpServletResponse resp){
return "admin/redefinirSenha";
}
}
Página de Teste (main.jsp)
<ul>
Testes de Anotações Shiro
<li>
<a href="${pageContext.request.contextPath}/acessarUsuario">Autenticação de Usuário</a>
</li>
<li>
<a href="${pageContext.request.contextPath}/acessarPapel">Verificação de Papel</a>
</li>
<li>
<a href="${pageContext.request.contextPath}/acessarPermissao">Verificação de Permissão</a>
</li>
</ul>