fev 19

Autenticação com Apache Shiro e JSF

Nesse post vou mostrar uma soluçao que eu adotei para integrar o JSF e JPA com o Shiro, não digo que seja a melhor ou a mais prática, mas é uma solução.

Primeiramente o que é Shiro?

Segundo o próprio site do Shiro :

“Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.”

Ou seja, é um framework Java de segurança.

Este post cobre apenas a parte de autenticação. Vamos ao que interessa então:

1) Adicionar as dependências

Para quem usa Maven :

 
<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-core</artifactId>
   <version>1.2.1</version>
</dependency>
<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-web</artifactId>
   <version>1.2.1</version>
</dependency>

Essa era a versão mais recente na época do post! Caso queira se certificar disso acesse http://mvnrepository.com/ e busque por Shiro.

2) Configurar o web.xml

Você precisará registrar o filtro do Shiro para filtrar suas requisições e um listener.

 
<listener>
   <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
   <filter-name>ShiroFilter</filter-name>
   <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>ShiroFilter</filter-name>
   <url-pattern>/*</url-pattern>
   <dispatcher>REQUEST</dispatcher>
   <dispatcher>FORWARD</dispatcher>
   <dispatcher>INCLUDE</dispatcher>
   <dispatcher>ERROR</dispatcher>
</filter-mapping>

 

3) Criar o arquivo shiro.ini na pasta WEB-INF onde está o web.xml

Você pode colocar esse arquivo em outro lugar , mas aí você precisará indicar isso no web.xml . Vamos adotar aqui a política “convenção sobre configuração”.

Este arquivo contém a configuração necessária para que o Shiro funcione.

Se você procurar no google achará diversas maneiras de configurá-lo. Aqui estamos bem básicos:

[main]

meurealm = meupacote.MeuRealm
securityManager.realms = $meurealm
authc.loginUrl=/login.xhtml
[users]
[roles]

[urls]
# enable authc filter for all application pages
/login.xhtml = authc
/secure/** = authc

 

Neste arquivo eu configurei o meu realm autenticador. Se quiser saber mais sobre realms leia http://shiro.apache.org/realm.html .

Em seguida atribuo aos realms do securityManager. Posso ter quantos realms eu quiser.
authc.loginUrl=/login.xhtml serve para indicar minah url de autenticação.
[users]
[roles]
Não são necessários aqui pois estou guardando-os no banco de dados.
Em [urls] eu declaro quais urls precisam de autenticação. Caso eu tenha páginas públicas basta fazer o seguinte:
/public/** = anom
Ou seja, todas as páginas embaixo do diretório public não necessitam de autenticação.
Em /secure/** = authc só será permitido o acesso a essas páginas caso haja usuário logado, essa verificação fica transparente para o desenvolvedor, uma vez que o filtro do shiro declarado no web.xml fará este trabalho.

 

4) Implementar o Realm autenticador

 
public class MeuRealm implements Realm {

@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
    throws AuthenticationException {
        String principal = (String) token.getPrincipal();

        MeuDAO dao = new MeuDAO();
        String credencial = dao.buscarCredencial(principal);

        if(credencial != null) {
             AuthenticationInfo info = new SimpleAuthenticationInfo(principal,credencial,getName());
             SimpleCredentialsMatcher cmatcher = new SimpleCredentialsMatcher();

             boolean credentialsMatch = cmatcher.doCredentialsMatch(token, info);
             if(credentialsMatch) {
                 return info;
             }

         }
         throw new AuthenticationException();

}

@Override
public String getName() {

return "MeuApp";
}

@Override
public boolean supports(AuthenticationToken token) {
   //valido o token 
     ...
   //tudo ok com ele
    return true;  
   //problema com o token
    return false;

}

 

Neste código eu implemento a interface Realm e nela eu preciso implementar os 3 métodos mostrados acima. Quando esta classe será chamada eu mostrarei mais pra frente.

Mas basicamente o que é preciso saber é o seguinte:

No método supports eu recebo um token e preciso verificar se este token é válido para meu realm, como só temos um tipo de autenticação posso retornar true direto.
No método getName deve-se retornar um nome único para esse Realm.

Já no método getAuthenticationInfo é onde temos que colocar a mão na massa. Segundo a documentação do Shiro deve-se implementá-lo da seguinte maneira, primeiro você verifica se existe o usuário contido no Principal do seu token e retorna a credencial dele. No meu caso eu retorno null caso não ache nenhum usuário, caso ache eu retorno a senha do usuário.
Em seguida você deve criar um CredentialMatcher para verificar se a credencial do token é válida, neste exemplo eu fiz a verificação mais simples que é a de igualdade.
Caso você utilize conexão segura e sua senha seja recebida em texto claro e você armazene-a em formato de hash SHA256, você pode utilizar o Sha256CredentialsMatcher por exemplo.
Caso a verificação das credenciais retorne true você deve retornar um novo objeto AuthenticationInfo. Caso não o ache a documentação diz para se retornar null, mas você pode fazer do jeito que quiser.

5) O ManagedBean de login

Aqui é bem simples também, primeiramente eu instancio um UsernamePasswordToken passando o usuário e senha digitados no formulário.
Em seguida eu busco o Subject, que é o conceito de usuário que o Shiro utiliza, através do método estático SecurityUtils.getSubject().
Por fim eu chamo o método login do Subject criado passando o token gerado.
Ao fazer isso o Shiro vai invocar o(s) Realm(s) criado(s) que você declarou no shiro.ini , e chamar o método supports, caso retorne true ele chamará o getAuthenticationInfo.
Caso este último retorne um objeto do tipo AuthenticationInfo ele irá prosseguir na execução, no meu caso redirecionando o usuário para a área restrita da aplicação.
Se retornar uma AuthenticationException, eu adiciono uma mensagem genérica no FacesContext de que o usuário é inválido.

 
public void login() throws IOException {
        FacesContext ctx = FacesContext.getCurrentInstance();
        UsernamePasswordToken token = new UsernamePasswordToken(
                usuario.getEmail(), usuario.getSenha());
        Subject currentUser = SecurityUtils.getSubject();
        try {
            currentUser.login(token);
            ctx.getExternalContext().redirect("secure/index.xhtml");
        } catch (AuthenticationException ae) {

            ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN,
                    "Usuário/senha inválido(s)!", "Usuário/senha inválido(s)!"));
        }

    }

 

6) Todo login precisa de um logout

Por fim para deslogar o usuário basta o seguinte

 
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();

Recomendo também redirecionar o usuário, pois não é feita a validação caso você esteja utilizando ajax.

 

Bom, é isso, sei que ficou um pouco confuso, mas eu tentei resumir ao máximo este assunto, uma vez que ele é bem mais complexo do que o que foi apresentado aqui. Pra quem for utilizar produtivamente  recomento a leitura da documentação antes.

fev 05

Dica rápida de compras na china

Acabei de receber minha segunda encomenda ontem. Comprei no fim do ano passado um headphone bluetooth e um capa pra kindle! O lugar? Aliexpress . Todas as compras vieram com rastreio, aí é só colocar lá no muambator e receber por email as notificações de mudança.

Antes eu comprava no DX, mas estava vindo sem rastreio e algumas das minhas encomendas não estavam chegando, aí é só dor de cabeça reclamando lá.  No Aliexpress não tem erro, 2 dias depois da compra é postado o produto e você recebe o código de rastreio. E se não chegar você abre uma disputa no site para reaver seu dinheiro. Não precisei disso ainda, e espero não precisar, mas já é uma medida de confiança a mais!

É isso pra quem comprava no DX e está achando o serviço deles ruim fica a dica!