Autorização com Shiro e Desenvolvimento Baseado em Anotações

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&gt> 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>

Tags: Apache Shiro Autorizacao Segurança Web Anotações Java Spring MVC

Publicado em 7-9 21:42