Tutorial: Começando com Spring Security

18 Jan 2010
5 mins read

Esse tutorial vai cobrir um cenário básico onde integra um módulo do framework Spring - Spring Security, utilizando autenticação via banco de dados, em uma aplicação web que já utiliza o Spring.

Como qualquer outro assunto relacionado à Spring, a curva de aprendizado em um pouco grande. Mas como qualquer outro assunto Spring, uma vez que você faz a primeira configuração e aprende, pode usá-la sempre em outras aplicações, ou seja, o famoso Ctrl C + Ctrl V.

Quando comecei a estudar Spring Security no final de dezembro, encontrei esses primeiros passos sugeridos na página do Spring Security.

E se você quiser configurar a parte de segurança na sua aplicação web utilizando Spring Security, segia os seguintes passos:

A primeira coisa que precisa fazer é adicionar os arquivos do framework no classpath da aplicação. Faça o download do Spring Security, e copie os seguintes arquivos da pasta dist; cole-os na pasta lib da sua aplicação:

Também é necessário fazer o download do Apache Commons Codec:commons-codec-1.3.jar

Vamos começar com a configuração dos XMLs:

Web.xml

Cole o seguinte código no arquivo web.xml. Deve ser inserido logo após ao final da tag /context-param.

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-security.xml
</param-value>
</context-param>

<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>

[/code]

applicationContext-security.xml

Foi feita uma referência ao arquivo applicationContext-security.xml dentro do web.xml. Vamos criá-lo.

Seguindo o tutorial da página do Spring Security, sugiro começar com o XML encontrado no tutorial de exemplo, e incrementá-lo aos poucos. Esse é o arquivo básico:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<?xml version="1.0" encoding="UTF-8"?>

<!--
- Sample namespace-based configuration
-
- $Id: applicationContext-security.xml 3019 2008-05-01 17:51:48Z luke_t $
-->

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

<global-method-security secured-annotations="enabled">
</global-method-security>

<http auto-config="true">
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
</http>

<!--
Usernames/Passwords are
rod/koala
dianne/emu
scott/wombat
peter/opal
-->
<authentication-provider>
<password-encoder hash="md5"/>
<user-service>
<user name="rod" password="a564de63c2d0da68cf47586ee05984d7" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" />
<user name="dianne" password="65d15fe9156f9c4bbffd98085992a44e" authorities="ROLE_USER,ROLE_TELLER" />
<user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" />
<user name="peter" password="22b5c9accc6e1ba628cedc63a72d57f8" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</beans:beans>
[/code]

Agora você pode tentar executar a aplicação.

Após tentar executar a sua app, e se a seguinte exception for lançada:

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
SEVERE: Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/applicationContext-security.xml]; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/Signature
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:420)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:342)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:124)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:92)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:123)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:423)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:353)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:45)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3764)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4216)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:736)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
at org.apache.catalina.core.StandardService.start(StandardService.java:448)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:700)
at org.apache.catalina.startup.Catalina.start(Catalina.java:552)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433)
[/code]

Download aspectjrt-1.5.4.jar e o adicione no classpath.

Vamos então fazer algumas mudanças no arquivo applicationContext-security.xml.

Primeira mudança: substitua o bloco de código abaixo

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<http auto-config="true">
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
</http>
[/code]

por

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<http auto-config="true">

<!-- Don't set any role restrictions on login.jsp -->
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />

<!-- Restrict access to ALL other pages -->
<intercept-url pattern="/**" access="ROLE_USER" />

<!-- Set the login page and what to do if login fails -->
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" />

</http>
[/code]

O atributo auto-config basicamente diz para o spring-security configurar as opções padrões sozinho, ou seja, o framework faz tudo automático.

A página login.jsp pode ser acessada por qualquer ROLE.

Restringir acessor à página delogin siginifcaria que ninguém poderia acessá-la. Ficaria muito estranho, posi como pode autenticar um usuário se este ainda não efetuou o login?

Note que a página de login é um jsp, e não uma action. A página de login não precisa ser acessada através de uma action.

Na configuração acima, também foi restringido o acesso a todas as outras página. Apenas os usuários com a ROLE_USER podem acessá-la.

Digamos que tenha mais uma role. Fazer o mapeamento das URLS para as roles é bem fácil. Dentro da tag http, coloque as URLS e as roles assim:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<intercept-url pattern="/admin/*.do" access="ROLE_ADMIN"/>
<intercept-url pattern="/manager/*.do" access="ROLE_MANAGER"/>
<intercept-url pattern="/**.do" access="ROLE_USER,ROLE_ADMIN, ROLE_MANAGER"/>
[/code]

É claro que você não quer colocar (nem deve) todos os usernames (usuários) e passwords (senhas) e suas respectivas roles no arquivo applicationContext-security.xml. Como então dizemos ao spring-security para obter essas informações do banco de dados?

Coloque o seguinte bloco de código no applicationContext-security.xml (substitua o bloco com os usuários e senhas)

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<!-- Configure the authentication provider -->
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource" />
</security:authentication-provider>

[/code]

Para isso é necessário já ter um dataSource criado (não vou abordar esse assunto nesse post).

Agora a pergunta: para isso funcionar, como é que o framework espera que o meu banco de dados se pareça?

A autenticação padrão do spring security requer que a estrutura do banco de dados seja dessa maneira:

[code lang="sql" firstline="1" toolbar="true" collapse="false" wraplines="false"]
CREATE TABLE users
(
username character varying(50) NOT NULL,
"password" character varying(50) NOT NULL,
enabled boolean NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (username)
);

CREATE TABLE authorities
(
username character varying(50) NOT NULL,
authority character varying(50) NOT NULL,
CONSTRAINT fk_authorities_users FOREIGN KEY (username)
REFERENCES users (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);

CREATE UNIQUE INDEX ix_auth_username
ON authorities
USING btree
(username, authority);

[/code]

Mas se desejar configurar as queries que são usadas, pode utilizar a sua própria estrutura do banco de dados, só deve tomar cuidado para que os atributos/colunas da sua query "casem" (sejam os mesmos) com os atributos esperados pelo framework.

Por exemplo? você quer apenas ter uma tabela com todas essas informações no banco de dados (ou até uma tabela com nome e colunas diferentes). Vamos ver como fica a configuração do XML:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<jdbc-user-service data-source-ref="dataSource"
authorities-by-username-query="select username,authority from users where username=?"/>
[/code]

Talvex você ainda também queira configurar outras páginas:

Access Denied: essa página será exibida caso o usuário tente acessar uma página que não pertença a sua role:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<http ... access-denied-page="/accessDenied.jsp">
...
</http>
[/code]

Default Target URL: essa é a página que o usuário será redirecionado caso seja autenticado com sucesso no login:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<http ... >
...
<form-login ... default-target-url="/home.do"/>
...
</http>
[/code]

Logout URL: essa é a página que o suusário será redirecionado quando fizer logout:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<http ... >
...
<logout logout-success-url="/home.do"/>
...
</http>
[/code]

Login Failure URL: essa é a página que o usuário será redirecionado caso a autenticação falhe (login). Geralmente é a própria página de login, com algum parâmetro indicando um erro:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<http ... >
...
<form-login ... authentication-failure-url="/login.jsp?login_error=1"/>
...
</http>
[/code]

Bem, é isso que você precisa para começar com Spring Security.

Na próxima semana, posto como a página login.jsp do Spring Security se parece.

Bons códigos!