Celestrini

  • HomePágina Inicial
  • ContatoEntre em contato
  • SobreSobre Celestrini

22 de março de 2010
Posted by admin

JAAS e JSF – Fácil, Fácil (Ou quase!)

Introdução

Quando falamos em JAAS, o que nos vem a cabeça geralmente é algo inflexível, difícil de usar. Embora JAAS tenha essa fama, veremos nesse post que não é bem assim. Abordaremos o uso de JAAS com JSF, utilizando o Glassfish como Servidor de Aplicação.

Antes de começar, vamos rever alguns conceitos:

  • Autenticação: é o processo de identificação de um “usuário” (pessoa, processo etc.) em um sistema. Isto é feito comparando-se as credenciais passadas pelo usuário com as esperadas pelo sistema. O método mais comum de autenticação é o uso de senhas, mas pode-se utilizar várias outras técnicas, como biometria.
  • Autorização: é o processo de verificação dos direitos que um usuário possui para acessar/manipular um determinado recurso do sistema. Um exemplo seria restringir o acesso de um cliente de um banco a apenas sua própria conta.
  • Realm: Um Realm é basicamente um banco de dados de usuários, podendo ser um arquivo com nome de usuário / senha + informações de grupos, tabelas de banco de dados ou até mesmo um diretório ldap ou qualquer outra coisa que você possa imaginar, como o sistema de autenticação do google. Um Realm pode ser usado por várias aplicações.
  • User: Um User(Usuário) é uma pessoa ou programa que deseja autenticar-se em nosso servidor / aplicação. Se você cria suas aplicações apenas para usuários reais, então estes são seus usuários. Se você também oferece webservices, outros programas acessando esses serviços também são usuários. Um usuário pertence a um Realm, por este motivo ele pode ser válido em várias aplicações. (veja Principal)
  • Role: Role(Papel) é um nome abstrato para a permissão de acesso a um grupo particular de recursos de uma aplicação. A Role pode ser comparada a uma chave que pode abrir uma porta. Muitas pessoas poder ter uma cópia da chave. A porta não se importa com sua identidade, apenas se você possui a chave.
  • Group: Group(Grupo) é um conjunto de usuários autenticados, classificados por traços comuns, definidos no Servidor de Aplicação..
  • Principal: Um Principal é um usuário autenticado no escopo de uma aplicação. O mesmo User(usuário) pode ter diferentes Principals em diferentes aplicações. Um Principal é identificado pelo seu nome e autenticado usando dados de autenticação (Credentials).
  • Security Attributes: são atributos associados com cada Principal, como: “é permitido o acesso a área administrativa”, ou coisas do tipo.
  • Credential: Contêm ou referencia atributos de segurança; são usadas para autenticar um Principal em um serviço Java EE (aplicação).

User e Role

Em nosso exemplo, cada usuário terá uma lista de papéis (roles) que o concederá acesso a determinadas funções do sistema.

Abaixo, temos duas classes: User e Role. Vejam que ambas implementam a interface Principal.

package com.celestrini.jaas.principals;

import java.security.Principal;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author Jordano Celestrini
 */
public class User implements Principal {

    private String name;
    private Set roles;

    public User(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    public Set getRoles() {
        if (roles == null) {
            roles = new HashSet();
        }
        return roles;
    }

    public void setRoles(Set roles) {
        if (this.roles == null) {
            this.roles = roles;
        }
    }
}
package com.celestrini.jaas.principals;

import java.security.Principal;

/**
 *
 * @author Jordano Celestrini
 */
public class Role implements Principal {

    private String name;

    public Role(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

LoginModule

A interface LoginModule descreve os métodos que devem ser implementados pelos fornecedores de tecnologias de autenticação. LoginModules são conectados em aplicações para fornecer um determinado tipo de autenticação (LDAP, Banco de Dados).

Em JAAS, existem várias formas de prover autenticação e autorização. Todas essas soluções, devem implementar a interface LoginModule.

O Glassfish oferece por padrão alguns LoginModules, entre eles:

  • LoginModule baseado em arquivos
  • LoginModule baseado em Banco de Dados
  • LoginModule para LDAP

Nessa solução, estaremos utilizando um LoginModule personalizado. Este LoginModule será um template, baseado no FileLoginModule que poderá ser adaptado à várias realidades. Veja o código abaixo:

package com.celestrini.jaas.login;

import com.celestrini.jaas.principals.Role;
import com.celestrini.jaas.principals.User;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

/**
 *
 * @author Jordano Celestrini
 */
public class AuthLoginModule implements LoginModule {

    private static Logger logger = Logger.getLogger(LoginModule.class.getName());
    // Status da autenticação
    private boolean succeeded = false;
    private boolean commitSucceeded = false;
    // Nome de usuário e senha fornecidos
    private String username;
    private char[] password;
    private User user;
    // Estado inicial
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map<String, ?> sharedState;
    private Map options;
    //Opções configuráveis
    private boolean clearPass = false;
    private boolean tryFirstPass = false;
    private boolean useFirstPass = false;
    private boolean storePass = false;
    //chave para recuperar o nome de usuário armazenado
    private static final String USERNAME_KEY = "javax.security.auth.login.name";
    //chave para recuperar a senha armazenada
    private static final String PASSWORD_KEY = "javax.security.auth.login.password";

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;
        this.options = options;

        //inicializa as opções configuradas
        tryFirstPass =
                "true".equalsIgnoreCase((String) options.get("tryFirstPass"));
        useFirstPass =
                "true".equalsIgnoreCase((String) options.get("useFirstPass"));
        storePass =
                "true".equalsIgnoreCase((String) options.get("storePass"));
        clearPass =
                "true".equalsIgnoreCase((String) options.get("clearPass"));
    }

    /**
     * <p>Inicia o processo de autenticação
     * do usuário (Autenticação - Fase 1)</p>
     * <p>Recupera o nome do usuário e a senha e
     * verifica a correspondencia das credenciais informadas</p>
     *
     * @return sempre true desde que este <code>LoginModule</code>
     *          não seja ignorado.
     * @exception FailedLoginException caso a autenticação falhe.
     * @exception LoginException caso este <code>LoginModule</code>
     *          seja incapaz de realizar a autenticação.
     */
    @Override
    public boolean login() throws LoginException {
        // attempt the authentication
        if (tryFirstPass) {

            try {
                // processa a autenticação buscando o
                // nome de usuário e a senha do estado compartilhado
                attemptAuthentication(true);

                //autenticação feita com sucesso
                succeeded = true;

                logger.log(Level.INFO, "Authentication using cached password has succeeded");

                //retorna sucesso
                return true;

            } catch (LoginException le) {
                //autenticação falhou -- tenta novamente abaixo, alertando

                //limpa o estado
                cleanState();

                //alerta
                logger.log(Level.WARNING, "Authentication using cached password has failed");
            }

        } else if (useFirstPass) {
            try {
                // processa a autenticação buscando o
                // nome de usuário e a senha do estado compartilhado
                attemptAuthentication(true);

                //autenticação feita com sucesso
                succeeded = true;

                logger.log(Level.INFO, "Authentication using cached password has succeeded");

                return true;

            } catch (LoginException le) {
                // authentication failed
                cleanState();
                logger.log(Level.WARNING, "Authentication using cached password has failed");
                throw le;
            }
        }

        logger.log(Level.INFO, "Acquiring password");

        // tenta autenticar usando o nome de usuário e a senha fornecidos
        try {
            attemptAuthentication(false);

            // authentication succeeded
            succeeded = true;

            logger.log(Level.INFO, "Authentication has succeeded");

            return true;

        } catch (LoginException le) {
            cleanState();
            logger.log(Level.WARNING, "Authentication has failed");
            throw le;
        }
    }

    /**
     * <p>Tenta autenticar.
     * Este método é o método responsável pela autenticação.
     * </p>
     *
     * @param usePasswdFromSharedState flag que indica ao método se deve
     *           recuperar a senha do estado compartilhado (sharedState).
     */
    @SuppressWarnings("unchecked")  // sharedState used as Map<String,Object>
    private void attemptAuthentication(boolean usePasswdFromSharedState)
            throws LoginException {

        //recupera o nome de usuário e a senha
        getUsernamePassword(usePasswdFromSharedState);

        /**
         * TODO: Fazer o processo de autenticação de acordo com o necessário
         * neste ponto temos o nome de usuário e a senha armazenados
         * nas variáveis globais username e password
         * agora basta ir no banco ou o que for e validá-los
         * se a informação for inválida, lance uma FailedLoginException
         * throw new FailedLoginException("Invalid username or password");
         * caso seja válido, armazene o usuário na variável global usuário
         * user = new User(username);
         *
         * Neste caso, para testes, colocaremos o nome de usuário
         * teste e a senha teste
         **/
        boolean loginSucess = false;

        if (this.username.equals("teste") && Arrays.equals(this.password, "teste".toCharArray())) {
            loginSucess = true;
        }

        if (!loginSucess) {
            // username not found or passwords do not match
            logger.log(Level.SEVERE, "Invalid username or password");
            throw new FailedLoginException("Invalid username or password");
        }

        //Salva o nome de usuário e a senha no estado compartilhado
        //somente se a autenticação for bem sucedida
        if (storePass
                && !sharedState.containsKey(USERNAME_KEY)
                && !sharedState.containsKey(PASSWORD_KEY)) {
            ((Map) sharedState).put(USERNAME_KEY, username);
            ((Map) sharedState).put(PASSWORD_KEY, password);
        }

        //criando o usuario (principals)
        user = new User(username);

        //add to the user roles
        user.getRoles().add(new Role("ADMIN"));

        //logging
        logger.log(Level.INFO, "User '" + username + "' successfully validated");
    }

    /**
     * <p>Recupera o nome de usuário e a senha
     * Este método não retorna nenhum valor
     * Em vez disso, o método seta as variáveis
     * globais nome e password</p>
     *
     * Note também que este método irá setar o
     * nome de usuário e a senha no estado compartilhado
     * caso LoginModules subsequentes queiram usá-los via tryFirstPass
     *
     * @param usePasswdFromSharedState boolean indica ao método
     *          se deve recuperar a senha do estado compartilhado (sharedState).
     */
    private void getUsernamePassword(boolean usePasswdFromSharedState)
            throws LoginException {

        if (usePasswdFromSharedState) {
            //usa a senha salva pelo primeiro módulo na pilha
            username = (String) sharedState.get(USERNAME_KEY);
            password = (char[]) sharedState.get(PASSWORD_KEY);
            return;
        }

        //recupera nome de usuário e senha
        if (callbackHandler == null) {
            throw new LoginException("Error: no CallbackHandler available "
                    + "to garner authentication information from the user");
        }

        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("username");
        callbacks[1] = new PasswordCallback("password", false);

        try {
            callbackHandler.handle(callbacks);
            username = ((NameCallback) callbacks[0]).getName();
            char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
            password = new char[tmpPassword.length];
            System.arraycopy(tmpPassword, 0,
                    password, 0, tmpPassword.length);
            ((PasswordCallback) callbacks[1]).clearPassword();
        } catch (IOException ioe) {
            throw new LoginException(ioe.toString());
        } catch (UnsupportedCallbackException uce) {
            throw new LoginException(
                    "Error: " + uce.getCallback().toString()
                    + " not available to garner authentication "
                    + "information from the user");
        }
    }

    /**
     * Completa a autenticação do usuário (Autenticação - Fase 2)
     *
     * <p> Este método é chamado se a autenticação global foi bem sucedida
     * (se todos os LoginModules do tipo REQUIRED, REQUISITE,
     * SUFFICIENT e OPTIONAL foram bem sucedidos)
     *
     * <p> Caso esta própria tentativa de autenticação do LoginModule
     *
     * Se a tentativa de autenticação do módulo de login for
     * bem sucedida (controlada ao recuperar o estado privado salvo pelos
     * metódos <code>login</code> e <code>commit</code>), então este método
     * irá associar <code>User</code> com o <code>Subject</code> localizado
     * no <code>LoginModule</code>. Se a tentativa de autenticação
     * do módulo de login falhar, então este método irá remover
     * qualquer estado originalmente salvo.
     *
     * @exception LoginException caso o commit falhe
     * @return true caso os métodos login e commit tenham
     *          sido completados com sucesso, falso caso contrário.
     */
    @Override
    public boolean commit() throws LoginException {
        if (succeeded == false) {
            return false;
        } else {
            if (subject.isReadOnly()) {
                cleanState();
                throw new LoginException("Subject is read-only");
            }
            //adicionar Principals ao Subject
            if (!subject.getPrincipals().contains(user)) {
                subject.getPrincipals().add(user);
            }

            logger.log(Level.INFO, "Authentication has completed successfully");
        }
        //em qualquer caso, limpa o estado
        cleanState();
        commitSucceeded = true;
        return true;
    }

    /**
     *<p>Aborta processo de autenticação (Autenticação - Fase 2)</p>
     *
     * <p> Este método é chamado se o processo de autenticação falhar
     * (os relevantes LoginModules dos tipos REQUIRED, REQUISITE,
     * SUFFICIENT e OPTIONAL não forem bem sucedidos). </p>
     *
     * Se a tentativa de autenticação do módulo de login for
     * bem sucedida (controlada ao recuperar o estado privado salvo pelos
     * metódos <code>login</code> e <code>commit</code>), então este método
     * irá limpar qualquer estado que foi originalmente salvo.
     *
     * @exception LoginException caso o metódo falhe
     * @return false caso o método login ou commit tenha falhado,
     *                            true, caso contrário
     */
    @Override
    public boolean abort() throws LoginException {
        logger.log(Level.INFO, "Authentication has not completed successfully");

        if (succeeded == false) {
            return false;
        } else if (succeeded == true && commitSucceeded == false) {

            //limpa o estado
            succeeded = false;
            cleanState();
            user = null;
        } else {
            // overall authentication succeeded and commit succeeded,
            // but someone else's commit failed
            logout();
        }
        return true;

    }

    /**
     * <p>Processo de Logout</p>
     *
     * <p>Este método remove os Principals
     * que foram adicionados pelo metódod <code>commit</code>
     *
     * @exception LoginException caso o logout falhe.
     * @return true em todos os casos desde que este <code>LoginModule</code>
     *          não seja ignorado.
     */
    @Override
    public boolean logout() throws LoginException {
        if (subject.isReadOnly()) {
            cleanState();
            throw new LoginException("Subject is read-only");
        }
        subject.getPrincipals().remove(user);

        //limpa o estado
        cleanState();
        succeeded = false;
        commitSucceeded = false;
        user = null;

        logger.log(Level.INFO, "Subject is being logged out");

        return true;
    }

    /**
     * <p>Limpa o estado em virtude de uma
     * tentativa de autenticação mal sucedida</p>
     */
    private void cleanState() {
        username = null;
        if (password != null) {
            Arrays.fill(password, ' ');
            password = null;
        }

        if (clearPass) {
            sharedState.remove(USERNAME_KEY);
            sharedState.remove(PASSWORD_KEY);
        }
    }
}

O método attemptAuthentication é o método responsável pela autenticação em sí. Este método deve ser alterado para prover a autenticação (via banco, arquivo xml, enfim,…).

Instalando a biblioteca no servidor

Para que o servidor reconheça nosso LoginModule, devemos adicioná-lo como uma biblioteca java dentro da pasta lib do nosso domínio (${GLASSFISH_V3_HOME}/glassfish/domains/domain1/lib).

Dentro da pasta conf (${GLASSFISH_V3_HOME}/glassfish/domains/domain1/config), existe um arquivo chamado “login.conf”. Você deve editá-lo, para que o servidor consiga “enxergar” o nosso LoginModule.

Adicione as linhas abaixo ao final do arquivo

authLoginModule {
com.celestrini.jaas.login.AuthLoginModule required;
};

Repare que apenas criamos um “alias” para o nosso LoginModule, chamando-o de “authLoginModule” e apontando para a classe com.celestrini.jaas.login.AuthLoginModule.

Pronto! O nosso módulo de login está criado e já está disponível para uso, basta iniciar (ou reiniciar) o servidor de aplicação.

Vamos agora ver como integrá-lo com nossa aplicação JSF.

Integrando JAAS com JSF

Vamos começar com o fomulário de login. Vejamos abaixo:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form id="form">
            <h:messages />

            <h:outputLabel value="Username: " />
            <h:inputText id="username" value="#{loginBean.username}"/><br />

            <h:outputLabel value="Password: " />
            <h:inputText id="password" value="#{loginBean.password}" />

            <h:commandButton value="Login" action="#{loginBean.login}"/>
        </h:form>
    </h:body>
</html>

Bom, até aqui, nada demais!

Vamos dar uma olhada no nosso Managed Bean de login:

package com.celestrini.jaas.web.security;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.security.auth.login.LoginException;

/**
 *
 * @author Jordano Celestrini
 */
@ManagedBean(name = "loginBean")
@RequestScoped
public class LoginBean {

    private String username;
    private String password;

    public String login() {
        String retorno = null;

        //instancia helper
        SecurityHelper jaasHelper = new SecurityHelper("authLoginModule");

        //tenta logar
        try {
            jaasHelper.authenticate(getUsername(), getPassword());
            retorno = "index.xhtml?faces-redirect=true";
        } catch (LoginException ex) {
            FacesContext context = FacesContext.getCurrentInstance();
            context.addMessage(null, new FacesMessage(ex.getMessage(), ex.getMessage()));
        }

        return retorno;
    }

    /**
     * @return the username
     */
    public String getUsername() {
        return username;
    }

    /**
     * @param username the username to set
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * @return the password
     */
    public String getPassword() {
        return password;
    }

    /**
     * @param password the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }
}

O nosso Managed Bean utiliza uma classe utilitária chamada SecurityHelper para fazer o processo de autenticação. A classe utilitária está logo abaixo:

package com.celestrini.jaas.web.security;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

/**
 *
 * @author Jordano Celestrini
 */
public class SecurityHelper {

    private LoginContext loginContext = null;
    private String loginModule;

    public SecurityHelper(String loginModule) {
        this.loginModule = loginModule;
    }

    public void authenticate(String userid, String password) throws LoginException {
        loginContext = new LoginContext(loginModule, new LoginCallback(userid, password));
        loginContext.login();
    }

    public Subject getSubject() {
        Subject result = null;
        if (null != loginContext) {
            result = loginContext.getSubject();
        }
        return result;
    }

    public static class LoginCallback implements CallbackHandler {

        private String userName = null;
        private String password = null;

        public LoginCallback(String userName, String password) {
            this.userName = userName;
            this.password = password;
        }

        @Override
        public void handle(Callback[] callbacks) {
            for (int i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof NameCallback) {
                    NameCallback nc = (NameCallback) callbacks[i];
                    nc.setName(userName);
                } else if (callbacks[i] instanceof PasswordCallback) {
                    PasswordCallback pc = (PasswordCallback) callbacks[i];
                    pc.setPassword(password.toCharArray());
                }
            }
        }
    }
}

Essa classe é responsável por chamar o nosso LoginModule atraves do objeto LoginContext. A classe LoginContext descreve os métodos básicos utilizados para autenticação e fornece uma maneira de desenvolver uma aplicação independente da tecnologia de autenticação.

Repare que passamos para o LoginContext um parâmetro do tipo String. Esse parâmetro é o nome do nosso LoginModule.

Problemas

Bom, mas nem tudo é 100%. A especificação Servlet prevê mecanismos de autenticação utilizando Realm.

Quando fazemos a autenticação programaticamente (como em nosso exemplo), a nossa aplicação não “fica sabendo” que isso aconteceu.

Dessa forma, ao tentar acessar um EJB, como o abaixo, nos deparamos com um problema, veja só:

package com.celestrini.jaas.web.business;

import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;

/**
 *
 * @author Jordano Celestrini
 */
@Stateless
public class TestBusiness {

    @RolesAllowed("ADMIN")
    public String getMessage() {
        return "OK! Funciona";
    }
}

Percebam, no código acima, que apenas os usuários com a role ADMIN podem acessar o método.

O grande problema é que, como a nossa aplicação web não conhece nosso mecanismo de autenticação, é como se o processo não tivesse acontecido e, por conseqüência, a autenticação não é propagada ao nosso EJB. Ao tentar acessá-lo, nos deparamos com a mensagem:

javax.ejb.AccessLocalException: Client not authorized for this invocation.

Como o EJB não sabe que estamos autenticados, ele lança uma exceção, informando que alguém não autorizado está tentando acessar o método.

A maneira mais simples de solucionar o problema é criar um Realm e um LoginModule personalizados para o Glassfish. O problema é que essas implementações são dependentes do fornecedor, o que significa que variam de servidor para servidor. Até então estávamos trabalhando à nível de especificação. :(

As duas classes abaixo representam o LoginModule e o Realm para o nosso exemplo:

package com.celestrini.glassfish.realm;

import com.sun.appserv.security.AppservPasswordLoginModule;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;

/**
 *
 * @author Jordano Celestrini
 */

public class AuthLoginModule extends AppservPasswordLoginModule {

    private static final Logger logger = Logger.getLogger(AuthLoginModule.class.getName());

    public AuthLoginModule() {
    }

    /**
     * Sobrescreve o método authenticateUser() da classe AppservPasswordLoginModule
     * Executa a autenticação do usuário
     * @throws javax.security.auth.login.LoginException
     */

    @Override
    protected void authenticateUser() throws LoginException {
        //Verifica se o Realm é do tipo AuthRealm
        if (!(_currentRealm instanceof AuthRealm)) {
            throw new LoginException("Realm not AuthRealm");
        }

        //Tenta autenticar usuário
        AuthRealm authRealm = (AuthRealm)_currentRealm;
        if (!authenticate(_username, _password)) {
            //Se o login falhar
            logger.log(Level.WARNING, "Authentication has failed");
            throw new LoginException("Authentication has failed for user  " + _username);
        }

        //Caso o login tenha sucesso
        logger.log(Level.INFO, "Authentication has succeeded for user {0}", _username);

        //Busca os grupos para o usuário autenticado
        String authenticatedGroups[] = {"CHEFE"};

        //Comita os dados
        commitUserAuthentication(authenticatedGroups);
    }

    /**
     * <p>Método privado para autenticar o usuário</p>
     */
    private boolean authenticate(String username, String password) throws FailedLoginException {
        /**
         * TODO: Fazer o processo de autenticação de acordo com o necessário
         * neste ponto temos o nome de usuário e a senha armazenados
         * nas variáveis globais username e password
         * agora basta ir no banco ou o que for e validá-los
         * se a informação for inválida, lance uma FailedLoginException
         * throw new FailedLoginException("Invalid username or password");
         * caso seja válido, armazene o usuário na variável global usuário
         * user = new User(username);
         *
         * Neste caso, para testes, colocaremos o nome de usuário
         * teste e a senha teste
         **/
        boolean loginSucess = false;

        if (username.equals("teste") && password.equals("teste")) {
            loginSucess = true;
        }

        if (!loginSucess) {
            // username not found or passwords do not match
            logger.log(Level.SEVERE, "Invalid username or password");
        }

        //logging
        logger.log(Level.INFO, "User ''{0}'' successfully validated", username);

        return loginSucess;
    }
}
package com.celestrini.glassfish.realm;

import java.util.*;

import com.sun.appserv.security.AppservRealm;
import com.sun.enterprise.security.auth.realm.*;

public class AuthRealm extends AppservRealm {

    private String jaasCtxName;

    @Override
    protected void init(Properties props) throws BadRealmException, NoSuchRealmException {
        jaasCtxName = props.getProperty("jaas-context", "authLoginModuleGF");
    }

    @Override
    public Enumeration getGroupNames(String string) throws InvalidOperationException, NoSuchUserException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public String getJAASContext() {
        return jaasCtxName;
    }

    /*
     * String com descrição do tipo de autenticação que está sendo utilizada
     */
    public String getAuthType() {
        return "AuthRealm";
    }
}

Depois de criar as classes, vá ao arquivo login.conf e registre o seu LoginModule. Dê a ele o nome authLoginModuleGF.

Depois disso, edite o arquivo domain.xml, dentro da pasta conf (${GLASSFISH_V3_HOME}/glassfish/domains/domain1/config) e crie um novo Realm:

<auth-realm classname="com.celestrini.glassfish.realm.AuthRealm" name="authRealm" />

Você também pode criar o Realm a partir do Console Administrativo do Glassfish.

Essa forma de autenticação mapeia as roles estaticamente por meio dos groups, de forma que é necessário identificar quais roles estão associadas a cada group. Para isso, adicione o código abaixo ao arquivo sun-web.xml

<security-role-mapping>
	<role-name>ADMIN</role-name>
	<group-name>CHEFE</group-name>
</security-role-mapping>

Esse xml diz que a role ADMIN está associada ao group CHEFE.

Modifique o arquivo login.xhtml, de forma que ele fique assim:

<form method="POST" action="j_security_check">
    <table border="0" cellspacing="5">
        <tr>
            <th align="right">Username:</th>
            <td align="left"><input type="text" name="j_username" /></td>
        </tr>
        <tr>
            <th align="right">Password:</th>
            <td align="left"><input type="password" name="j_password" /></td>
        </tr>
        <tr>
            <td align="right"><input type="submit" value="Login" /></td>
        </tr>
    </table>
</form>

Pronto! Agora é só testar!

Conclusão

Bom pessoal, vimos que JAAS não é nenhum bicho de sete cabeças. Realmente as coisas são feitas de forma bastante manual, mas ele provê todas as funcionalidades necessárias para autenticar e autorizar usuários dentro de um ambiente JAVA EE. O post pode ter sido pouco detalhado em algumas coisas, mas a idéia era dar um ponto de partida para que todo possam ter por onde começar.

Espero que de alguma forma esse post possa ajudar a todos.

Recursos

Código-fonte do projeto
http://java.sun.com/javaee/6/docs/tutorial/doc/gijrp.html
https://glassfish.dev.java.net/javaee5/security/faq.html#diffauthmodulerealm
http://roneiv.wordpress.com/2008/03/15/using-webauthentication-in-jboss/
http://blogs.sun.com/nasradu8/entry/loginmodule_bridge_profile_jaspic_in
http://java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html
http://dominikdorn.com/2010/02/jaas-authentication-jsf2-terminology/
http://blogs.sun.com/nithya/entry/groups_in_custom_realms
http://blogs.sun.com/phendley/entry/creating_and_using_a_glassfish

1 Comment

Posted Under Glassfish JAAS JSF Segurança

3 Comments

Gabriel Morrison
24 de março de 2010

Cara, parabéns pelo post! Ficou muito bem explicado! Vou estudar esse projeto que você fez e ver se consigo colocar no meu :)
Obrigado pela ajuda =)
Abraços

Leave a comment

* = Required

    • Posts
    • Twitter
    • Flickr
     

    JAAS e JSF –...

    Glassfish

    Sorry... I have not linked my Twitter
    to my blog yet
    Sorry... I have not set my Flickr
    account up yet
  • Categories

    • Glassfish
    • JAAS
    • JSF
    • Segurança
  • Archives

    • 2010
      • março

This site is using the Handgloves WordPress Theme
Designed & Developed by George Wiscombe

Subscribe via RSS