Tutorial de JAAS

Fábio Viana

Uma introdução a segurança em Java com JAAS



Tutorial de JAAS

1 - Introdução

O JAAS (Java Authentication and Authorization Service) é um conjunto que APIs que permite que as aplicações Java tenham um controle autenticação e de acesso. O JAAS implementa uma versão Java do framework padrão Pluggable Authentication Module (PAM), e suporta autorização baseada em usuário. Isso permite que aplicações fiquem independentes desse controle de segurança.

Serve para controlar permissões de vários tipos de recursos: arquivos, diretórios, conteúdos, URLs.

Para aplicações web, basta seguir os passos abaixo.

Quando usamos este padrão de segurança, devemos estar cientes que este modulo está a nível de servidor de aplicação e não de aplicação, ou seja, este modulo de autenticação será executado pelo servidor de aplicação, antes mesmo de acessar a aplicação.

2 - Criação do modulo de login
Existe uma interface definida no j2ee que é usada para efetuar o login: javax.security.auth.spi.LoginModule.
Devemos implementá-la, conforme o exemplo abaixo:

01  package br.com.guj.security.principals;
02  import java.security.Principal;
03  import java.util.Set;
04  
05  /**
06   @author fabio.viana
07   */
08  public class User implements Principal{
09      private String name;
10      private Set roles;
11      
12      public User(String name){
13          this.name = name;
14      }
15      
16      public String getName() {
17          return name;
18      }
19      
20      public Set getRoles() {
21          return roles;
22      }
23  
24      public void setRoles(Set roles) {
25          if (this.roles == null)
26              this.roles = roles;
27      }
28  }


01 package br.com.guj.security.principals;
02  import java.security.Principal;
03  
04  /**
05   @author fabio.viana
06   */
07  public class Role implements Principal{
08      private String name;
09      
10      public Role(String name){
11          this.name = name;
12      }
13      
14      public String getName() {
15          return name;
16      }
17  }


001 package br.com.guj.security;
002  import java.sql.*;
003  import java.util.*;
004  import javax.naming.*;
005  import javax.security.auth.*;
006  import javax.security.auth.callback.*;
007  import javax.security.auth.login.LoginException;
008  import javax.security.auth.spi.LoginModule;
009  import br.com.guj.security.principals.*;
010  
011  /**
012   @author fabio.viana
013   */
014  public class GujLoginModule implements LoginModule {
015      private boolean commitSucceeded = false;
016      private boolean succeeded = false;
017  
018      private User user;
019      private Set roles = new HashSet();
020  
021      protected Subject subject;
022      protected CallbackHandler callbackHandler;
023      protected Map sharedState;
024      private String dataSourceName;
025      private String sqlUser;
026      private String sqlRoles;
027  
028      public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
029          this.subject = subject;
030          this.callbackHandler = callbackHandler;
031          this.sharedState = sharedState;
032          dataSourceName = (Stringoptions.get("dataSourceName");
033          sqlUser = (Stringoptions.get("sqlUser");
034          sqlRoles = (Stringoptions.get("sqlRoles");
035      }
036  
037      public boolean login() throws LoginException {
038          // recupera o login e senha informados no form
039          getUsernamePassword();
040  
041          Connection conn = null;
042          try {
043              // obtem a conexão
044              try {
045                  Context initContext = new InitialContext();
046                  Context envContext = (ContextinitContext.lookup("java:/comp/env");
047                  DataSource ds = (DataSourceenvContext.lookup(dataSourceName);
048                  conn = ds.getConnection();
049              catch (NamingException e) {
050                  succeeded = false;
051                  throw new LoginException("Erro ao recuperar DataSource: " + e.getClass().getName() ": " + e.getMessage());
052              catch (SQLException e) {
053                  succeeded = false;
054                  throw new LoginException("Erro ao obter conexão: " + e.getClass().getName() ": " + e.getMessage());
055              }
056              // valida o usuario
057              validaUsuario(conn);
058          finally {
059              if (conn != null) {
060                  try {
061                      conn.close();
062                  catch (SQLException e) {
063                  }
064              }
065          }
066          // acidiona o usuario e roles no mapa de compartilhamento
067          sharedState.put("javax.security.auth.principal", user);
068          sharedState.put("javax.security.auth.roles", roles);
069  
070          return true;
071      }
072  
073      public boolean commit() throws LoginException {
074          // adiciona o usuario no principals
075          if (user != null && !subject.getPrincipals().contains(user)) {
076              subject.getPrincipals().add(user);
077          }
078          // adiciona as roles no principals
079          if (roles != null) {
080              Iterator it = roles.iterator();
081              while (it.hasNext()) {
082                  Role role = (Roleit.next();
083                  if (!subject.getPrincipals().contains(role)) {
084                      subject.getPrincipals().add(role);
085                  }
086              }
087          }
088          
089          commitSucceeded = true;
090          return true;
091      }
092  
093      public boolean abort() throws LoginException {
094          if (!succeeded) {
095              return false;
096          else if (succeeded && !commitSucceeded) {
097              succeeded = false;
098          else {
099              succeeded = false;
100              logout();
101          }
102  
103          this.subject = null;
104          this.callbackHandler = null;
105          this.sharedState = null;
106          this.roles = new HashSet();
107  
108          return succeeded;
109      }
110  
111      public boolean logout() throws LoginException {
112          // remove o usuario e as roles do principals
113          subject.getPrincipals().removeAll(roles);
114          subject.getPrincipals().remove(user);
115          return true;
116      }
117      
118      /**
119       * Valida login e senha no banco
120       */
121      private void validaUsuario(Connection connthrows LoginException {
122          String senhaBanco = null;
123          PreparedStatement statement = null;
124          ResultSet rs = null;
125          try {
126              statement = conn.prepareStatement(sqlUser);
127              statement.setString(1, loginInformado);
128              rs = statement.executeQuery();
129              if (rs.next()) {
130                  senhaBanco = rs.getString(1);
131              else {
132                  succeeded = false;
133                  throw new LoginException("Usuário não localizado.");
134              }
135          catch (SQLException e) {
136              succeeded = false;
137              throw new LoginException("Erro ao abrir sessão: "
138                      + e.getClass().getName() ": " + e.getMessage());
139          finally {
140              try {
141                  if (rs != null)
142                      rs.close();
143                  if (statement != null)
144                      statement.close();
145              catch (Exception e) {
146  
147              }
148          }
149  
150          if (senhaInformado.equals(senhaBanco)) {
151              user = new User(login);
152              recuperaRoles(conn);
153              user.setRoles(roles);
154              return;
155          else {
156              throw new LoginException("Senha Inválida.");
157          }
158      }
159      
160      /**
161       * Recupera as roles no banco
162       */
163      public void recuperaRoles(Connection connthrows LoginException {
164          PreparedStatement statement = null;
165          ResultSet rs = null;
166          try {
167              statement = conn.prepareStatement(sqlRoles);
168              statement.setString(1, loginInformado);
169              rs = statement.executeQuery();
170              while (rs.next()) {
171                  roles.add(new Role(rs.getString(1)));
172              }
173              roles.add(new Role("LOGADO"));
174          catch (SQLException e) {
175              succeeded = false;
176              throw new LoginException("Erro ao recuperar roles: " + e.getClass().getName() ": " + e.getMessage());
177          finally {
178              try {
179                  if (rs != null)
180                      rs.close();
181                  if (statement != null)
182                      statement.close();
183              catch (Exception e) {
184  
185              }
186          }
187      }
188  
189      /**
190       * Login do usuário.
191       */
192      protected String loginInformado;
193  
194      /**
195       * Senha do usuário.
196       */
197      protected String senhaInformado;
198  
199      /**
200       * Obtem o login e senha digitados
201       */
202      protected void getUsernamePassword() throws LoginException {
203          if (callbackHandler == null)
204              throw new LoginException("Error: no CallbackHandler available to garner authentication information from the user");
205  
206          Callback[] callbacks = new Callback[2];
207          callbacks[0new NameCallback("Login");
208          callbacks[1new PasswordCallback("Senha"false);
209          try {
210              callbackHandler.handle(callbacks);
211              loginInformado = ((NameCallbackcallbacks[0]).getName();
212              char[] tmpPassword = ((PasswordCallbackcallbacks[1]).getPassword();
213              senhaInformado = new String(tmpPassword);
214              ((PasswordCallbackcallbacks[1]).clearPassword();
215          catch (java.io.IOException ioe) {
216              throw new LoginException(ioe.toString());
217          catch (UnsupportedCallbackException uce) {
218              throw new LoginException("Error: " + uce.getCallback().toString() " not available to garner authentication information from the user");
219          }
220      }
221  }



O método initialize() é invocado sempre que uma nova autenticação é solicitada. São passados como parametros o Subject, CallbackHandler, o mapa de objetos compartilhados e o mapa de opções do login.config (sessão 3). O mais importante destes parametros para nós será o options, pois nele receberemos os parametros de sql e datasource.
O método login() é invocado quando é enviado os dados (login e senha) de autenticação.
O método commit() é invocado quando o login() obtém sucesso.
O método abort() é invocado quando o login() não obtém sucesso.
O método logout() é invocado quando o usuário desloga da aplicação.

Agora, com as classes de login criadas, devemos gerar um jar e adicionar no classpath do servidor de aplicação.
No tomcat, basta colocar o jar em $CATALINA_HOME/common/lib.

Obs.: Principal é a interface usada para acessar o login (no caso da classe User) e a role (no caso da classe Role)



3- Configurações
Devemos agora configurar o nosso login. Para que o servidor de aplicação use nosso modulo de login, devemos criar um arquivo (login.config) contendo as informações necessárias para carregar o modulo.

Ele deve ter o seguinte formato:

1 NOME_DO_MODULO {
2      CLASSE_DE_LOGIN_1 (requerid) (parametro1=valor1..parametroN=valorN);
3      CLASSE_DE_LOGIN_2 (requerid) (parametro1=valor1..parametroN=valorN);
4  }


O flag requerid, indica se a autenticação naquela classe é requerida. Podemos ter vários validadores de login (classe LoginModule) para o mesmo modulo de login.
Caso queira-se autenticar no banco e no ldap, basta implementar o LoginModule pra cada um e colocar no login.config.

No nosso exemplo ficaria assim:
1  guj {
2      br.com.guj.security.GujLoginModule required
3          dataSourceName="jdbc/GUJ"
4          sqlUser="select senha from tb_usuario where login=?"
5          sqlRoles="select id_role from tb_usuario_roles where login=?"
6      ;
7  };


A partir de agora, toda vez que iniciarmos o servidor de aplicação, será necessário informar o arquivo de configuração, desta forma:
-Djava.security.auth.login.config=$CATALINA_HOME/conf/login.config

Até aqui, o servidor de aplicação já estará configurado com nosso modulo.



4- Segurança na aplicação
Agora temos que adicionar a segurança em nossa aplicação.
Para que nossa aplicação faça uso do modulo de segurança, devemos adicionar um realm no contexto.

01 ...
02      <!-- Restrições -->
03      <security-constraint>
04          <display-name>cadastro de cientes</display-name>
05          <web-resource-collection>
06              <web-resource-name>cadastro de cientes</web-resource-name>
07              <url-pattern>/cliente.do</url-pattern>
08          </web-resource-collection>
09          <auth-constraint>
10              <role-name>ADM</role-name>
11              <role-name>CAD_CLIENTE</role-name>
12          </auth-constraint>
13      </security-constraint>
14      
15      <security-constraint>
16          <display-name>help da app</display-name>
17          <web-resource-collection>
18              <web-resource-name>help da app</web-resource-name>
19              <url-pattern>/help.do</url-pattern>
20          </web-resource-collection>
21          <auth-constraint>
22              <role-name>LOGADO</role-name>
23          </auth-constraint>
24      </security-constraint>
25  ...
26      <!-- Lista de Roles -->
27      <security-role>
28          <description>Quando usuario estiver logado</description>
29          <role-name>LOGADO</role-name>
30      </security-role>
31      
32      <security-role>
33          <description>Administrador, pode fazer tudo</description>
34          <role-name>ADM</role-name>
35      </security-role>
36      
37      <security-role>
38          <description>Para cadastrar cliente, deve se ter esta role</description>
39          <role-name>CAD_CLIENTE</role-name>
40      </security-role>
41      


Repare que a url /cliente.do possue duas roles, será avaliado se o usuario posue alguma delas.
Repare tambem que foi criado uma role LOGADO. Ela serve pra validar aquelas url's que basta que o usuario esteje logado.

Agora só falta criar o formulário de login. Crie um arquivo para o formulario (login.jsp). Observe abaixo o exemplo:
1  <form method="POST" action="<%=request.getContextPath()%>/j_security_check">
2      Usuário: <input type="text" name="j_username" size="15"><br>
3      Senha: <input type="password" name="j_password" maxlength="20" size="15">
4      </form>
5     


Sempre coloque os nomes dos campos de login e senha como j_username e j_password.
Este j_security_check é apenas para que o servidor de aplicação identifique que se trata de uma tentativa de login.

Crie tambem um arquivo de erro (erro.jsp):
1 Usuario ou senha inválido(s).<br>
2     <form method="POST" action="<%=request.getContextPath()%>/j_security_check">
3      Usuário: <input type="text" name="j_username" size="15"><br>
4      Senha: <input type="password" name="j_password" maxlength="20" size="15">
5      </form>
6      

Para concluir, adicione mais esta configurão no web.xml. Trata-se do mapeamento dos arquivos login.jsp e erro.jsp.
1      <login-config>
2          <auth-method>FORM</auth-method>
3          <realm-name>default</realm-name>
4          <form-login-config>
5              <form-login-page>/login.jsp</form-login-page>
6              <form-error-page>/erro.jsp</form-error-page>
7          </form-login-config>
8      </login-config>
9       

Pronto!

Quando for acessado a url /cliente.do, o servidor de aplicação irá identificar que é necessário autenticação,
se não foi feita anteriromente ele irá redirecionar para a pagina de login até que o usuario seja autenticado.



5 - Conclusão
O jaas é um pouco limitado e cheio de restrições, mas para controlar segurança e autenticação é o mais recomendado.
Caso precise de adicionar regras na autenticação, como por exemplo expiração de senha, use um filter só para ver se o usuario está com a senha expirada.

Caso precise do usuario autenticado e/ou a(s) role(s) em um Servlet, Action... basta usar os metodos:

1     
2      HttpServletRequest.getUserPrincipal()// retorna o User
3      HttpServletRequest.isUserInRole("ADM")// retorna se o usuario possue a role informada
4      User user = (User)HttpServletRequest.getUserPrincipal();
5      user.getRoles()// roles do usuario
6     



Copyright © 2002-2006 GUJ | Todas as marcas e marcas registradas que aparecem no GUJ são de propriedade de seus respectivos donos