20 de abril de 2011

Java EE 6: Segurança com Spring Security 3.0.5

Quando se fala em sistemas online, logo deve-se pensar em como tratar a parte da segurança dos dados, de acesso... nessa postagens vamos trabalhar com a segurança de acesso ao sistema, ou seja, o login.
Para fazer esse controle de login vou utilizar o framework Spring Security na versão 3.0.5, estarei disponibilizando os jar's usados, pois não aconselho baixar do próprio site deles, andei fazendo uns testes e o que está disponível agora nessa mesma versão não bate com o que eu baixei a um tempo atrás, então para fazer este projeto o mesmo pode ser baixado por aqui.
O Spring Security trata a autenticação de usuários da seguinte forma: o bloqueio de acesso pode ser feito a diretórios ou a arquivos, ou seja o usuário deve ter a permissão de acesso para o determinado diretório ou arquivo, simplificando... suponhamos que eu tenha um diretório admin, com o Spring Security eu posso definir que apenas usuários que tenham permissão de administrador possa ter acesso ao conteúdo desse diretório. Existem duas formas de fazer a autenticação de acesso, com usuários pré-definidos no xml de configuração ou fazendo a validação de usuário em um banco de dados. Nessa postagem vamos tratar essas duas formas.
Inicialmente vamos criar o nosso projeto de base que vai funcionar para os exemplos com ou sem banco de dados.
Estrutura


Obs: veja que temos um diretório admin e dentro dele um xhtml com o nome de index, então nesse projeto temos 2 index, o da raiz poderá ser acessado sem fazer login, já o que se encontra dentro da do diretório precisará de permissão para ser acessado. Vamos começar com a codificação:
/index.xhtml
<h:body>
        Usuario não precisa estar logado...
</h:body>

/negado.xhtml
<h:body>
        Você não tem permissão de acessar este diretorio.
        <a href="index.jsf">Voltar </a>
</h:body>

/falha.xhtml
<h:body>
        Login ou senha incorretos...
        <a href="index.jsf">Voltar</a>
</h:body>

/admin/index.xhtml
<h:body>
        Usuario logado com sucesso!
        <a href="../logout">Sair</a>
</h:body>
O /logout eu vou explicar mais pra frente... é coisa da config do Spring Security.
Depois de criar as páginas para fazer nosso teste, vamos colocar as bibliotecas no projeto, você pode criar uma biblioteca no Netbeans (Ferramentas/Bibliotecas/Nova biblioteca...), ou adicionar diretamente os jar's ao projeto (no projeto, botão direito em Bibliotecas e em add jar).
Os jar's são:



Outra coisa em comum nos exemplos que vamos fazer é a configuração do Spring Security no WEB-INF/web.xml, dentro dele adicione a seguinte configuração (pode ser logo abaixo da tag web-app):

<!-- Spring security -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

<!-- Fim spring security -->

Se você executar ele neste ponto, dará erro, pois com essa configuração ele exige que você tenha um /WEB-INF/applicationContext.xml, então para começar crie um arquivo xml com o nome de applicationContext dentro do WEB-INF...

Segurança de acesso com usuários pré-definidos (sem BD)
Toda a configuração a partir de agora é feita dentro do arquivo applicationContext.xml citado acima.
Abaixo vou postar como ele fica e depois explico as tags:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <http auto-config="true" access-denied-page="/negado.jsf">
        <intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
        <logout invalidate-session="true" logout-success-url="/index.jsf" logout-url="/logout"/>
    </http>

    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="adm" password="123" authorities="ROLE_ADMIN" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>
beans: é a raiz do nosso xml, nela é definido os namespace do Spring Security.

http: caso não tenha percebido, nesse projeto nós não fizemos uma página de login, isso porque ao setar a propriedade auto-config como true, o próprio Spring Security cria uma página de login (ao final da postagem ensino como personalizar sua página de login), e a propriedade access-denied-page se refere à pagina que deve ser mostrada quando um usuário for logado mas não tem acesso a um diretório ou arquivo (esse arquivo negado.jsf se refere à nossa página criada lá na estrutura do projeto).
intercept-url: esta tag é a responsável por definir quais as permissões que o usuário deve ter para acessar determinados diretórios ou arquivos, no exemplo veja que estamos definindo que o usuário deve ter a permissão ROLE_ADMIN para ter acesso aos arquivos do diretório admin.
logout: esta tag é a responsável por chamar a servlet que desloga o usuário, a propriedade logout-sucess-url se refere a página pra onde o usuário deve ser enviado ao ser deslogado, e o logout-url seria o nome da servlet... lembra que na página /admin/index.jsf nós temos "../logout", pois bem, é a esta configuração que ele está se referindo.

authentication-manager: gerencia a autenticação.
authentication-provider: provedor de autenticação.
user-service: é onde deve-se definir os usuários.
user: é o usuário propriamente dito, a propriedade name é o login do usuário, password é a senha e authorities são as permissões do usuário. Um usuário pode ter mais de uma permissão, estas devem ser separadas por vírgula na propriedade authorities, ficando por exemplo assim:
authorities="ROLE_ADMIN,ROLE_USER".

Bom, assim nosso login com usuário pré-definido já está funcionando, basta testar...

Segurança de acesso autenticando o usuário no banco de dados (com BD)
Para este exemplo vamos utilizar o MySQL, precisamos ter uma tabela com a seguinte estrutura:


Para fazer este projeto, aconselho apenas fazer uma cópia do projeto acima, pois só precisaremos alterar o applicationContext.xml, o mesmo deve ficar assim (logo abaixo do código vou explicar as novas tags):
<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <http auto-config="true" access-denied-page="/negado.jsf">
        <intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
        <logout invalidate-session="true" logout-success-url="/index.jsf" logout-url="/logout"/>
    </http>

    <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
        <beans:property name="url" value="jdbc:mysql://localhost:3306/5tads" />
        <beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <beans:property name="username" value="root" />
        <beans:property name="password" value="admin" />
    </beans:bean>
    
    <authentication-manager>
        <authentication-provider>
            <jdbc-user-service data-source-ref="dataSource"
                users-by-username-query="SELECT username, password, 'true' as enable FROM users WHERE username=?"
                authorities-by-username-query="SELECT username, authority FROM users WHERE username=?"
            />
        </authentication-provider>
    </authentication-manager>

</beans:beans>
Algumas tags são padrão tanto para usar com ou sem banco, por exemplo a http. Agora vamos ver quais as tags que temos de diferente do exemplo anterior (sem BD).

bean: entre várias possibilidades de utilização desta, nesse exemplo estamos usando ela para definir o dataSource que fará a conexão com o banco de dados MySQL, a propriedade id é nome que identifica esse bean... já a propriedade class se refere a classe do Spring Security que é utilizada para fazer a conexão.
property: serve para setar uma propriedade, neste exemplo é usada para setar propriedades diferentes referentes a conexão ao banco de dados. Vamos ver as propriedades pelos name...
url: é a url de acesso ao banco de dados, 5tads é a database que eu estou usando
driverClassName: é o nome do driver do MySQL
username: é o usuário de acesso ao banco de dados
password: é a senha de acesso ao banco de dados.

Veja que o nosso authentication-provider mudou... vamos ver o que ele mudou:
Quando utilizamos o banco de dados para fazer a validação dos usuários, não temos o porque de definir os usuários manualmente no xml, por isso o nosso user-service foi substituído por jdbc-user-service.
jdbc-user-service: a propriedade data-source-ref utiliza como referencia o nosso bean de conexão acima com o id de dataSource, já a users-by-username-query é a query responsável por selecionar o usuário pelo username... quanto ao "'true' as enable" na query, seria por exemplo uma coluna booleana na nossa tabela users dizendo se o usuário está ativo ou não. A outra propriedade é a authorities-by-username-query que tem uma query que é responsável por pegar as permissões do usuário pelo username.

Feito isso... o nosso controle de login com BD está pronto, e é só testar! 


Página de login
Como já falei anteriormente, o Spring Security já tem sua página de login que é chamada pela servlet : /spring_security_login ... A página é a seguinte:


Vou criar a minha página de login... dentro de páginas web crie uma nova página JSF, com o nome de login.xhtml, ela será a responsável por substituir a página do Spring Security, o código dela é o seguinte:
<h:body>
        <a href="index.jsf">Retornar para a Página Inicial</a>
        <form action="j_spring_security_check" method="post">
            <h:panelGrid columns="2">
                <h:outputText value="Usuario" />
                <h:inputText id="j_username" />
                <h:outputText value="Senha" />
                <h:inputSecret id="j_password" />
            </h:panelGrid>
            <input type="submit" value="Efetuar Login" />
        </form>
</h:body>
Mas agora você deve estar se perguntando... porque não usar o form do próprio JSF e o commandButton no lugar do input? Então, pelos testes que eu fiz, não é possível usar o form e o commandButton do JSF, ele acaba não funcionando direito, e olha que não foi por falta de testes...
Mas vamos entender algumas coisinhas.. como já falei, precisamos usar o form e o input do próprio HTML, outras coisas são de extrema importância para o funcionamento, a action do form precisa ser a servlet j_spring_security_check, assim como os id's dos campos precisam ser j_username e j_passsword ... se eu não me engano quando usa-se o Spring Security de forma personalizada (daria uma longa postagem) tem como usar outros id's, mas não é o nosso foco hoje.
Agora temos que informar ao applicationContext.xml que temos uma página de login, para isso, dentro da tag http, basta informar a seguinte linha:
<form-login login-page="/login.jsf" authentication-failure-url="/falha.jsf"/>
A propriedade login-page é a responsável por dizer qual é a nossa página de login, e a authentication-failure-url é a página que informa que o login e a senha estão incorretos (fizemos essa página lá no inicio da postagem).

Pra quem precisar de algo mais complexo como um login personalizado, deixo a seguinte referencia: Spring Security 3 - MVC Integration: Using A Custom Authentication Manager me ajudou muito quando precisei implementar um assim!

Referências: 

17 de abril de 2011

JPA 2.0: Utilizando os NamedQuery

Segundo a especificação do JEE 6, NamedQuery é definida como:
"Specifies a static, named query in the Java Persistence query language. Query names are scoped to the persistence unit. The NamedQuery annotation can be applied to an entity or mapped superclass."
Traduzindo... NamedQuery's são consultas Java Persistence estáticas e pré-definidas.
Como são elas?
Vamos usar como exemplo a postagem de como gerar classes anotadas a partir do banco de dados com o NetBeans, ao gerar as classes a partir do banco, o NetBeans já gera algumas NamedQuery's, logo abaixo da anotação @Entity, por exemplo vou pegar as NamedQuery's geradas sobre a classe Categoria.
@NamedQueries({
    @NamedQuery(name = "Categoria.findAll", query = "SELECT c FROM Categoria c"),
    @NamedQuery(name = "Categoria.findByCodigo", query = "SELECT c FROM Categoria c WHERE c.codigo = :codigo"),
    @NamedQuery(name = "Categoria.findByNome", query = "SELECT c FROM Categoria c WHERE c.nome = :nome")})
Nada lhe impede de criar novas consultas, ou alterar o name das mesmas.
Como utilizá-las?
Aproveitando o projeto de gerar classes anotadas a partir do banco de dados (link acima), vou usar as NamedQuery de Categoria (postada logo acima). Só irei mostrar os métodos da minha classe DAO (tem vários exemplos de CRUD no blog...). Não tem muito o que explicar porque o método por sí só é bem claro.
Primeiro vou usar a NamedQuery de listar todas as categorias que é a: Categoria.findAll
public List<Categoria> listar(){
 List<Categoria> categorias = null;
 try {
  categorias = em.createNamedQuery("Categoria.findAll")
   .getResultList();
 } catch (Exception e) {
  e.printStackTrace();
 }
 return  categorias;
}
O meu em é o meu EntityManager, dele eu uso o método createNamedQuery, e passo o nome do NamedQuery que está lá na minha Categoria.

public Categoria buscar(Integer codigo){
 Categoria categoria = null;
 try {
  categoria = (Categoria) em.createNamedQuery("Categoria.findByCodigo")
   .setParameter("codigo", codigo)
   .getSingleResult();
 } catch (Exception e) {
  e.printStackTrace();
 }
 return categoria;
}
Para selecionar apenas um resultado não é muito diferente, basta apenas setar os parameters, que são aqueles precedidos por : (dois pontos), nesse caso é o codigo. E para pegar um resultado eu uso o getSingleResult().

Dá pra ver que não é muito complicado utilizar os NamedQuerys...
Detalhe, caso haja mais parâmetros para setar em um namedQuery, basta ir concatenando um ao outro, por exemplo, suponhamos que o namedQuery usado abaixo tivesse mais um parametro que seria nome, basta fazer isso:
categoria = (Categoria) em.createNamedQuery("Categoria.findByCodigo")
    .setParameter("codigo", codigo)
    .setParameter("nome", "Andreia")
    .getSingleResult();
}

E assim, ficamos por aqui com mais uma postagem ;)