Segurança EJB
Autenticação e Autorização
• Proteger um aplicativo remete a duas funções principais: autenticação e autorização. • É importante ter certeza de que os usuários terão acesso apenas aos dados e
operações para as quais eles estão credenciados e também garantir que hackers não possam contornar os mecanismos de segurança do aplicativo.
Autenticação e Autorização
• A autenticação é o processo de verificar a identidade do usuário. Autenticando, você prova ser quem você diz ser.
• Autorização é o processo de determinar se um usuário previamente autenticado possui a permissão de executar uma certa ação.
Autenticação
• Autenticação
• A especificação EJB não especifica como a autenticação acontece; também não define como o servidor de aplicação armazena e recupera informação de autenticação. Embora defina como informações de segurança são propagadas a partir de um cliente para um servidor.
• O provedor EJB deve decidir como prover esses serviços.
• Na realidade, vamos mostrar um pouco mais que segurança EJB, vamos mostrar segurança JEE.
Autorização
• Depois que um usuário é autenticado por um mecanismo específico do fornecedor, deve ser verificado se ele está autorizado a invocar um método EJB de um session bean particular (em EJB o controle de acesso tem granularidade de método).
• A autorização é realizada, associando um ou mais papéis com um determinado usuário e, em seguida, atribuindo permissões de método baseadas em papéis. • Papéis são usados para identificar um grupo de usuários – por exemplo,
JAAS
• Segurança EJB é largamente baseada em JAAS (Java Authentication and Authorization Service).
• JAAS essencialmente separa o sistema de autenticação da aplicação Java EE.
• JAAS sabe como conversar com sistemas de autenticação como Microsoft’s Active Directory ou Lightweight Directory Access Protocol (LDAP).
• O servidor JEE internamente usa JAAS para fazer autorização nas camadas web e EJB.
Segurança declarativa EJB
• De forma semelhante a CMTs, com a segurança declarativa, você diz ao container o que espera da autorização através de anotações (ou ainda através de arquivos de configuração).
Segurança declarativa EJB
• A anotação @RunAs é similar ao comando sudo do Unix (permite, por exemplo, executar uma comando como administrador).
Segurança programática EJB
• Segurança declarativa é poderosa, mas há situações nas quais é necessário um controle maior sobre a segurança. Por exemplo, pode ser necessário alterar o comportamento do método de acordo com o papel do usuário.
• É importante observar que segurança declarativa e programática não são mutuamente excludentes.
Autenticação no Glassfish
• Vamos criar um exemplo de autenticação utilizando o Glassfish e um banco de dados relacional
• No Glassfish, é o que chamaremos de saltRealm.
• Primeiro, serão definidas tabelas em um banco de dados para armazenar informações ligadas aos usuários e grupos.
• Segundo, será criada uma biblioteca (um arquivo .jar) que deve conter no mínimo duas classes:
• Uma subclasse de com.sun.appserv.security.AppservPasswordLoginModule.
• Uma subclasse de com.sun.appserv.security.AppservRealm.
Tabelas e Campos
• TB_USUARIO.TXT_LOGIN: é o login do usuário, que é único.
• TB_USUARIO.TXT_PASSWORD: senha em forma de hash criptográfico (Wikipedia: Função_hash_criptográfica).
• TB_USUARIO.TXT_SAL: é o sal. A senha é concatenada com um valor aleatório, não-secreto conhecido como sal, antes de ser aplicada a função hash.
• TB_GRUPO.TXT_NOME: é o nome do grupo, que é único.
Classes
• AppservPasswordLoginModule
• Nossa subclasse terá apenas que sobrescrever um único método:
• protected void authenticateUser() throws LoginException; • AppservRealm
• Nossa subclasse terá que sobrescrever pelo menos três métodos:
• public synchronized void init(Properties properties) throws BadRealmException, NoSuchRealmException; //lê as propriedades de um arquivo XML de
configuração do Glassfish.
• public String getJAASContext(); //retorna o nome do realm.
• public Enumeration getGroupNames(String username) //retorna uma
Fragmento de
código da classe
SaltRealm
public class SaltRealm extends AppservRealm {
@Override
public String getJAASContext() {
return "saltRealm";
}
@Override
public Enumeration getGroupNames(String username) { Vector<String> vector = new Vector<String>();
//recupera a lista de grupos através de um SQL. List<String> groups = getGroupList(username);
for (String group : groups) { vector.add(group); } return vector.elements(); } } }
Baixe o código fonte:
https://github.com/marcosifpe/Bib liotecaConsumidor/trunk/mavenpr oject1/saltRealm
Fragmento de
código da classe
SaltLoginModule
public class SaltLoginModuleextends AppservPasswordLoginModule { @Override
protected void authenticateUser() throws LoginException { SaltRealm realm = (SaltRealm) _currentRealm;
if (realm.authenticateUser(_username, _password)) {
List<String> groupsList =
realm.getGroupList(_username); String[] groups = new String[groupsList.size()];
int i = 0;
for (String group : groupsList) groups[i++] = group;
commitUserAuthentication(groups);
} else { throw new LoginException("Invalid login!"); } }
Configurações
no Glassfish
<auth-realm
classname="softwarecorporativo.saltrealm.SaltRealm"name="saltRealm"> <property name="jaas-context" value="saltRealm" />
<property name="jta-data-source"value="jdbc/Biblioteca"/> <property name="password-sql-query"
value="SELECT txt_password, txt_sal
FROM tb_usuario WHERE txt_login = ?"
/>
<property name="groups-sql-query" value="SELECT g.txt_nome
FROM tb_usuario u, tb_grupo g, tb_usuario_grupo ug WHERE u.id = ug.id_usuario
AND g.id = ug.id_grupo AND u.txt_login = ?"/> <property name="hash-algorithm"value="SHA-256" /> <property name="charset"value="UTF-8" />
</auth-realm>
O arquivo
<glassfish-home>\domains\domain1\config\ domain.xml deve ser alterado com o realm criado.
Configurações no Glassfish
• Ao arquivo <glassfish-home>\domains\domain1\config\login.conf deve ser adicionado o seguinte valor:
saltRealm {
softwarecorporativo.saltrealm.SaltLoginModule required; };
• Para possibilitar a realização do login de forma programática (a que vamos utilizar), é necessário adicionar a seguinte linha ao arquivo
<glassfish-home>\domains\domain1\config\server.policy:
grant codeBase "file:jar-file-path" {
permission com.sun.appserv.security.ProgrammaticLoginPermission "login"; };
Configurações na
Aplicação
<security-role-mapping>
<!-- utilizado na aplicação JEE -->
<role-name>usuario</role-name>
<!-- recuperado do banco de dados via “realm” -->
<group-name>usr</group-name> </security-role-mapping>
<security-role-mapping>
<role-name>administrador</role-name> <group-name>admin</group-name> </security-role-mapping>
• No arquivo WEB-INF\glassfish-web.xml, adicionar os papéis da aplicação JEE.
• Observe que role-name é o nome do papel na aplicação e
group-name é o nome do grupo que é recuperado do realm.
• Baixe a aplicação em:
https://github.com/marcosifpe/Bibl ioteca/
Configurações na
Aplicação
<login-config>
<auth-method>FORM</auth-method> <realm-name>saltRealm</realm-name> <form-login-config>
<form-login-page>/faces/publico/login.xhtml</form-login-page> <form-error-page>/faces/publico/login.xhtml</form-error-page> </form-login-config>
</login-config>
• No arquivo WEB-INF\glassfish-web.xml, é necessário informar como vai ser realizado o login. • No caso, o login será realizado
através de um form, em uma página
xhtml.
• O realm que será utilizado para autenticação é o saltRealm. • Para mais informações, ver
documentação em:
https://docs.oracle.com/cd/E13222 _01/wls/docs81/webapp/web_xml. html#1019996
Configurações na
Aplicação
<security-constraint>
<display-name>Páginas Administrativas</display-name> <web-resource-collection>
<web-resource-name>Área Protegida</web-resource-name> <description>Área Protegida</description>
<url-pattern>/faces/admin/*</url-pattern> <http-method>GET</http-method>
<http-method>POST</http-method>
<!-- Demais métodos HTTP omitidos por motivo de espaço. -->
</web-resource-collection> <auth-constraint>
<description/> <!– Apenas administradores terão acesso. -->
<role-name>administrador</role-name> </auth-constraint>
<user-data-constraint> <!-- Será utilizado HTTPS. -->
<transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint>
</security-constraint>
• Ainda no arquivo WEB-INF\glassfish-web.xml, é necessário informar quais papéis da aplicação JEE terão acesso a que áreas (páginas) da aplicação. • Para mais informações, ver
documentação em:
https://docs.oracle.com/cd/E13222_ 01/wls/docs81/webapp/web_xml.ht ml#1017885
Páginas
Administrativas
• As páginas listadas na figura ao lado foram protegidas através do web-resource-collection da página anterior.
Segurança
Declarativa em
EJB’s
@Stateless @LocalBean
@DeclareRoles({ADMINISTRADOR, USUARIO}) //Informa os papéis utilizados neste EJB.
public class LivroService extendsService<Livro> {
@RolesAllowed({ADMINISTRADOR}) //Apenas administradores podem salvar livros.
public void salvar(Livro livro) {
checkExistence(LIVRO_POR_ISBN, livro.getIsbn()); entityManager.persist(livro);
}
@TransactionAttribute(SUPPORTS)
@RolesAllowed({USUARIO}) //Apenas usuários podem consulta a lista de livros.
public List<Livro> getLivros() {
returngetResultList(Livro.LIVROS); }
@TransactionAttribute(SUPPORTS) @PermitAll //O método é de livre acesso.
public Livro getLivro(String isbn) {
returngetSingleResult(LIVRO_POR_ISBN, new Object[]{isbn}); } } • As anotações @DeclareRoles, @RolesAllowed e @PermitAll , no código ao lado, permitem a segurança declarativa (relativa à
Segurança
Programática
em EJB’s
@Stateless @LocalBean @TransactionManagement(CONTAINER)public class EditoraServiceextendsService<Editora> { @Resource private SessionContext sessionContext; @TransactionAttribute(REQUIRED)
public void salvar(Editora editora) {
//Apenas administradores podem salvar uma editora.
if(sessionContext.isCallerInRole(ADMINISTRADOR)) {
checkExistence(EDITORA_POR_NOME, editora.getNome()); entityManager.persist(editora);
} else throw new EJBAccessException(); }
@TransactionAttribute(SUPPORTS)
public List<Editora> getEditoras() {
returngetResultList(EDITORAS); } } • Observe o objeto SessionContext e a invocação do método sessionContext.isCallerInRole (ADMINISTRADOR)
Captcha
•Até o momento, nossa aplicação está utilizando HTTPS, autenticação e autorização JEE. •No entanto, desejamos ainda que um robô não consiga realizar autenticação no formulário
web. Logo, utilizaremos um captcha (https://pt.wikipedia.org/wiki/CAPTCHA) na nossa aplicação.
•Mais especificamente, utilizaremos o Google reCaptcha. Mais informações em:
reCaptcha
<context-param>
<param-name>CAPTCHA_URL</param-name> <param-value>
https://www.google.com/recaptcha/api/siteverify </param-value>
</context-param> <context-param>
<param-name>PUBLIC_CAPTCHA_KEY</param-name> <param-value>*******************</param-value> </context-param>
<context-param>
<param-name>PRIVATE_CAPTCHA_KEY</param-name> <param-value>*******************</param-value> </context-param>
•Após ter se registrado no
Google reCaptcha, o usuário
recebe uma chave pública e uma secreta para utilizar o
reCaptcha na aplicação.
•Na aplicação, armazenaremos essas chaves no arquivo
web.xml.
•A url servirá para acessarmos um webservice do Google que fará a verificação do desafio do
Login com reCaptcha
• É necessário adicionar, na página de login (login.xhtml), um javascript e um div, conforme explicado na documentação disponível no site da Google.
<script src='https://www.google.com/recaptcha/api.js' /> <div class="g-recaptcha" data-sitekey= "#{facesContext.externalContext.getInitParameter('PUBLIC_CAPTCHA_KEY')}“ />
Login com reCaptcha
• O Google disponibiliza um webservice via url
https://www.google.com/recaptcha/api/siteverify para fazer a verificação.
• A aplicação que utiliza reCaptcha deve enviar os parâmetros requeridos e receberá uma resposta em formato JSON.
Login com reCaptcha
• Na Biblioteca EJB3, a classe jsf.beans.Recaptcha é responsável conectar o webservice do Google e verificar se a resposta JSON foi true. Ou seja, se o usuário respondeu corretamente ao desafio do reCaptcha.
Fragmento de
Código da Classe
LoginBean
public String login() {
try {
facesContext = FacesContext.getCurrentInstance(); Recaptcha recaptcha = new Recaptcha(facesContext);
if (recaptcha.validar()) { //verifica o captcha
HttpServletRequest request = (HttpServletRequest)
facesContext.getExternalContext().getRequest(); request.login(usuario, senha); //tenta realizar o login
facesContext.getExternalContext().getSession(true); } else { //caso o captcha falhe
return "falha"; //tratamento de mensagem de erro omitido }
} catch (ServletException ex) { //caso o login falhe
return "falha"; //tratamento de mensagem de erro omito. }
return "sucesso"; }
• Observe que usuario e senha são atributos do bean jsf gerenciado
jsf.beans.LoginBean e passados via
página login.xhtml.
• Após verificar se a informação do
captcha é válido, é realizado o login através da invocação request.login(usuario, senha).
• Esse login será realizado utilizando a validação de saltRealm.
Fragmento de
Código da Classe
LogoutBean
@ManagedBean(name = "logoutBean") @ViewScoped
public class LogoutBean implements Serializable {
public String logout() throwsServletException {
FacesContext fc = FacesContext.getCurrentInstance(); HttpSession session =
(HttpSession) fc.getExternalContext().getSession(false);
if(session != null) {
session.invalidate(); //Invalida a sessão do usuário.
}
HttpServletRequest request =
(HttpServletRequest) fc.getExternalContext().getRequest(); request.logout(); //Realiza o logout no servidor JEE.
return "sair"; }
}
• Para sair da aplicação, dois passos são necessários:
1. invalidar o HttpSession associado ao usuário;
2. Realizar o logout via
HttpServletRequest, que retira as
informações de indentidade do usuário associadas correntemente ao servidor JEE.