Jeveaux's Weblog
Tudo certo e nada resolvido
Tudo certo e nada resolvido
Recentemente ministrei um curso sobre Segurança em aplicações JavaEE, onde, grande parte do treinamento é a utilização e configuração do JAAS (Java Authentication and Authorization Service) abordando Basic e Form Authentication usando os realms: File, JDBC e LDAP.
Aproveitando o embalo do treinamento e principalmente pela empolgação dos alunos da turma quando viram quão simples e fácil é usar o JAAS, vou mostrar a configuração de autenticação e autorização em uma aplicação JavaEE usando Basic Authentication e JDBC Realm.
O JAAS (Java Authentication and Authorization Service) é a API padrão do Java para controle de acesso e autorização em aplicações JavaEE. Com JAAS é possível autenticar e validar usuários e certificados, bem como controlar a possibilidade de acesso e/ou utilização de recursos na aplicação (arquivos, diretórios, URLs, conteúdo, etc). Resumidamente o JAAS cuida de:
O exemplo a seguir foi feito usando o Eclipse, Glassfish_v2 e MySQL, e não há nenhum segredo obscuro, simplesmente criaremos um Dynamic Web Project e usaremos o console do Glassfish para criar o realm.
O ponto chave para a configuração do JDBC Realm é o banco de dados (tabelas de usuário e grupos). Criaremos o schema com o SQL a seguir. Basicamente são apenas duas tabelas: usertable e grouptable.
A configuração do schema no realm será feita adiante e este exemplo está simples desta forma apenas para ficar o mais claro possível na configuração do realm. Certamente no seu caso você já terá suas tabelas de usuários e grupos e para usá-los basta fazer a configuração do realm de acordo com a sua realidade, alterando os valores das propriedades de configuração.
1 2 3 4 5 6 | CREATE DATABASE learn_jaas; USE learn_jaas; CREATE TABLE usertable(userid VARCHAR(10) PRIMARY KEY, password VARCHAR(32) NOT NULL); CREATE TABLE grouptable(userid VARCHAR(10), groupid VARCHAR(20) NOT NULL, PRIMARY KEY (userid, groupid)); ALTER TABLE grouptable ADD CONSTRAINT FK_USERID FOREIGN KEY(userid) REFERENCES usertable(userid); COMMIT; |
E agora vamos inserir alguns usuários e grupos para os testes, conforme o SQL abaixo. Os usuários serão: user, guest e admin e os grupos serão: users, e admins. Reparem que os grupos estão no plural, enquanto os usuários estão no singular. Este é um importante detalhe, a seguir, na configuração das roles usaremos os grupos para aplicar as regras e não os usuários.
1 2 3 4 5 6 | INSERT INTO usertable VALUES ('user', 'user'); INSERT INTO grouptable VALUES ('user', 'users'); INSERT INTO usertable VALUES ('guest', 'guest'); INSERT INTO grouptable VALUES ('guest', 'users'); INSERT INTO usertable VALUES ('admin', 'admin'); INSERT INTO grouptable VALUES ('admin', 'admins'); |
Com o banco de dados pronto ainda precisamos de um passo antes do realm, precisaremos criar agora o Connection Pool e o DataSource para este banco de dados. O JNDI Name deste DataSource será usado na criação do realm, portanto, este passo é essencial para o funcionamento do exemplo. Entretanto, como o foco aqui não é a criação do DataSource, darei apenas o JNDI Name conforme usei no exemplo: learn-jaas-jdbc.
Agora só precisamos criar o realm, o nome usado para ele será learn-jaas-realm. No console do glassfish vamos em: Configuration >> Securiy >> Realm >> New. As propriedades deverão ser preenchidas de acordo com a imagem e a tabela abaixo (se o seu schema for diferente é só ajustar os valores):
| Propriedade | Valor |
| jaas-context | jdbcRealm |
| datasource-jndi | learn-jaas-jdbc |
| user-table | usertable |
| user-name-column | userid |
| password-column | password |
| group-table | grouptable |
| group-name-column | groupid |
| digest-algorithm | none |
Com a infra-estrutura necessária criada e funcionando, partiremos para a aplicação. Este será um ponto bem simples, a aplicação não terá nada além de 4 páginas JSP, web.xml e sun-web.xml. No Eclipse, criaremos um Dynamic Web Project usando o glassfish como servidor (JavaEE 5). As páginas citadas anteriormente ficarão dispostas conforme o projeto abaixo:
Em cada página index.jsp dos diretórios admin, public e users há um dizer assim: “Welcome Admin!”; “Welcome Guest!”; “Welcome User!”; respectivamente. E em /index.jsp há apenas os links para as demais páginas. Estes diretórios serão protegidos posteriormente.
O próximo passo consiste na criação das roles em nossa aplicação. Este costuma ser o ponto onde os iniciantes se confundem muito, por isso houve aquela distinção entre os nomes dos usuários e os grupos anteriormente. A criação das roles é livre, podemos criá-las como bem entendermos, o único ponto de atenção aqui é que a role é associada ao grupo, ou seja, uma role será aplicada para todos os usuários de um determinado grupo.
Então, em WEB-INF/sun-web.xml criaremos as roles associadas com os seus respectivos grupos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <security-role-mapping> <role-name>user-role</role-name> <group-name>admins</group-name> <group-name>users</group-name> </security-role-mapping> <security-role-mapping> <role-name>admin-role</role-name> <group-name>admins</group-name> </security-role-mapping> <security-role-mapping> <role-name>guest-role</role-name> <group-name>admins</group-name> <group-name>users</group-name> <group-name>guests</group-name> </security-role-mapping> |
As roles foram criadas com o sufixo -role apenas para ficar bem claro que são roles e não usuários ou grupos. Vejam que as roles estão associadas aos grupos e não aos usuários.
Este é um momento onde aplicaremos as regras hierárquicas e organizacionais da empresa, repare que o grupo admins aparece em todas as roles, afinal de contas, não queremos barrar ao administrador do sistema o acesso às páginas públicas ou de usuários, certo?
Próximo passo: Configuração do web.xml! A configuração da autenticação em si não passa de 4 linhas de xml, informaremos o tipo (método) de autenticação (pode ser none, basic, form e digest/certificate) e qual o nome do realm associado:
1 2 3 4 | <login-config> <auth-method>BASIC</auth-method> <realm-name>learn-jaas-realm</realm-name> </login-config> |
O que precisaremos agora é, com base nas roles, criar as regras de acesso na aplicação. Elas podem ser feitas de várias formas e como podemos usar URL Pattern para definir onde a regra será aplicada, faremos isso por diretórios, combinando os diretórios com as roles (admin, user e guest):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <security-constraint> <web-resource-collection> <web-resource-name>paginas admnistrativas</web-resource-name> <url-pattern>/admin/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin-role</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>paginas para usuários</web-resource-name> <url-pattern>/users/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>user-role</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>paginas para visitantes</web-resource-name> <url-pattern>/public/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>guest-role</role-name> </auth-constraint> </security-constraint> |
Repare que o código ficou grande, mas é muito repetitivo. Os pontos de atenção são o elemento url-pattern, onde é gerada a regra para onde as restrições serão aplicadas e, principalmente, o elemento role-name, onde definimos qual a role está associada com aquela regra. Mais uma vez repare que a regra de acesso é associada à role, já a role, por sua vez está associada com um grupo, que por sua vez está associado com um usuário.
Agora é só fazer o deploy e executar o projeto. Lembre-se das senhas (ou consulte no banco de dados) e faça o teste de acesso em cada página.
Para facilitar disponibilizei o projeto para download, basta clicar aqui. (por algum motivo alheio a minha compreensão o wordpress não deixou o nome do arquivo igual ao original, então, baste colocar um ponto antes do ‘tar’, deve ficar project-to-learn-jass.tar.gz)
15 de janeiro de 2009 - 23:51
Parabéns pelo artigo Jevô. É muito bom para a comunidade Java brasileira possuir um artigo de alto nível sobre JAAS.
21 de janeiro de 2009 - 12:48
Parabéns… a dias procurando material. Não havia encontrado nada bem claro. Já havia entendido todo o funcionamento do JAAS porém não conseguia implementar nada. E o Logout ?
21 de janeiro de 2009 - 13:01
Olá Rogério, obrigado pelo feedback.
Sobre o logout, você pode fazer de maneira programática usando o método logout da classe LoginContext.
Abraço
14 de fevereiro de 2009 - 14:14
É possível definir as roles em banco de dados tb?? para não precisar definir estáticamente em um arquivo xml, pois minha aplicação necessita possiblitar que o administrador possa definir regras de acesso para os usuários. Obrigado
16 de fevereiro de 2009 - 15:26
“…Sobre o logout, você pode fazer de maneira programática usando o método logout da classe LoginContext….”
Também estou procurando algo sobre o logout. Mas toda referência que encontro é “session.invalidate()”.
Mas como o LoginContext.logout() seria executado ?
Alguns outros servidores como o Websphere adicionam cookies (ltpa) que inviabilizam o session.invalidate() como solução de logout.
Abraço
16 de fevereiro de 2009 - 15:43
@sebastiao
O session.invalidate() você poderá usar quando estiver, de alguma forma, fazendo o login/logout do usuário de forma manual, trabalhando com algum objeto na sessão. Para garantir é melhor você remover diretamente o objeto da sessão, desta forma você não correrá riscos.
Já o logout mencionado, da LoginContext, você usará quando estiver trabalhando com autenticação através de JAAS.
19 de fevereiro de 2009 - 16:29
Jeveaux,
Acho que não fui claro.
Quando programei meu LoginModule, implementei também um método logout(). Quando faço “logout” na minha aplicação Web usando JAAS, a única coisa que faço é invalidar a sessão e invalidar os eventuais cookies ltpa do WAS.
A questão é … quando o método logout() do meu LoginModule será disparado ? É necessário disparar ? Não fica lixo em memória dessa forma ?
Eu deveria além de invalidar a sessão e cookies, de alguma forma disparar LoginContext.logout() ? Como ?
Abraço
27 de fevereiro de 2009 - 17:06
Fala Jeveaux, blz?
Ótimo post, “simprão de tudo”, por isso mesmo muito fácil de entender.
Cara sempre que vejo um tutorial sobre jaas as permissões nas páginas ficam no web.xml, e se eu quiser manter isso no banco? entendeu minha dúvida?
Outra coisa e pra controlar acesso á uma parte do código, como por exemplo um campo, ou um link?
27 de fevereiro de 2009 - 17:24
@Luiz e @Diego
Sobre armazenar as roles em banco de dados, creio que não há nada ‘default’ pra isso. Dá pra usar um arquivo de policy com o controle de autorização, isso dá sim. Mas nada impede que implementemos um novo LoginModule para usar o banco de dados.
@Sebastiao
Você pode disparar o logou junto com o invalidate, não teria problemas. Mas é estranho, pois ao fazer o invalidate os cookies deveriam ser eliminados completamente e, com isso, a autenticação seria perdida.
@Luiz
Sobre a exibição de componentes na página de acordo com a permissão do usuário vai depender de que framework de view que você está usando, se não houver suporte a JAAS você vai ter que fazer ‘manualmente’.
17 de julho de 2009 - 10:30
Existe uma maneira de usarmos chaves primarias substitutas como idUsuario e idGrupo ao inves de usarmos os nomes (que no caso sao chaves naturaris) ?
grande abraço
3 de dezembro de 2009 - 12:36
Jeveaux, exelente conteúdo,parabéns e obrigado!
Eu precisarei autenticar usando informações de mainframe. Existe Realm para “mainframe” ao invés de jbdcRealm?
Outra coisa que notei é que no
não fala se é application ou container.Sendo assim, por default esta autenticação não seria gerenciada pelo container,certo? me perguntei, como pode haver a configuração do realm no glasfish se não é autenticado por container?Fiquei confuso.
Obrigado.
4 de dezembro de 2009 - 13:40
Olá Leandro,
Eu não conheço nenhum realm que possa ser utilizado diretamente com mainframe não =( Talvez (muito talvez), e ainda dependendo de qual mainframe for possa ser que existe algum conector ou algo do tipo para facilitar na comunicação, mas realmente eu não conheço praticamente nada nessa área, não posso ajudar muito.
28 de janeiro de 2010 - 22:03
A documentação da Sun sobre JAAS é uma merda, já li tudo que você imagina e ainda não entendi qual é a dele. Entra as dúvidas, quando uso LoginModule com CallbackHandler ou quando devo fazer diretamente no AS como noseu exemplo?
1 de fevereiro de 2010 - 13:24
jeveaux,
no final das contas descobri que não precisarei fazer um “realm” para mainframe. O que meu cliente faz é carregar um ldap com as informações do mainframe e então a aplicação obtém deste”repositório”.
Ficou mains fácil,certo?rsss
Grato pela atenção.
12 de abril de 2010 - 18:02
ué.. mas como a aplicação passa o usuário que deverá ser autenticado pelo jaas?
12 de abril de 2010 - 18:09
Pelo que aprendi no decorrer de muito sofrimento, é responsabilidade do servidor fazer o logout no container J2EE.
Cada um faz de uma maneira, no JBoss você precisa invalidar a sessão, no Websphere, dependendo da configuração, você precisa invalidar ou excluir um cookie ltap2, e por aí vai.
Abraço,
12 de abril de 2010 - 18:09
Ops, o cookie correto é ltpa2.
23 de maio de 2010 - 18:21
Olá,
Estou estudando sobre o JAAS, tentei fazer o exemplo descrito acima mas não estou obtendo sucesso, configurei o pool de conexões e o realm como descrito, mas não ta funcionando. Quando eu tento acessar /admin/index.jsp aparece uma caixa de diálogo solicitando usuário e senha, e quando informo os dados a caixa de diálogo volta a aparecer, isso ocorre por 3 vezes, na terceira sou direcionado para uma tela de erro:
HTTP Status 401 –
——————————————————————————–
type Status report
message
description This request requires HTTP authentication ().
Alguma ajuda?
23 de maio de 2010 - 19:55
Olá Geraldo,
Aparentemente está tudo funcionando, só tem um detalhe: a senha deve estar errada. O status http 401 significa acesso não autorizado (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html). De uma conferida no banco de dados e veja se a senha está batendo com a que você está digitando.
Abraço,
31 de maio de 2010 - 23:25
Olá, consegui resolver o problema era erro de conexão com o banco de dados, agora tenho outra dúvida, no request existe o método request.isUserInRole(String role) o qual verifica se o usuário logado está na role passada por parâmetro certo? tentei usar este método mas sempre me retorna false, vc sabe me dizer o porque disso? existe alguma configuração a mais que preciso fazer?
obrigado
1 de junho de 2010 - 22:12
Olá, consegui usar corretamente o método request.isUserInRole(String roleName), meu objetivo em usar tal método era restringir o acesso de um método em específico, ou seja, além de controlar o acesso por url pattern eu posso controlar acesso a métodos tbm. Para isso tive que adicionar no web.xml o seguinte:
Adm
admin-role
para cada role criada em sun-web.xml.
fica a dica para quem passar por aki
flw