Tutorial: Começando com Spring Security
Posted by Loiane | Posted in Spring Security | Posted on 18-01-2010
14
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:
- spring-security-core-2.0.4.jar
- spring-security-core-tiger-2.0.4.jar
- spring-security-acl-2.0.4.jar
- spring-security-taglibs-2.0.4.jar
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.
<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>
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:
<?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>
Agora você pode tentar executar a aplicação.
Após tentar executar a sua app, e se a seguinte exception for lançada:
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)
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
<http auto-config="true">
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
</http>
por
<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>
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:
<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" />
É 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)
<!-- Configure the authentication provider --> <security:authentication-provider> <security:jdbc-user-service data-source-ref="dataSource" /> </security:authentication-provider>
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:
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);
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:
<jdbc-user-service data-source-ref="dataSource"
authorities-by-username-query="select username,authority from users where username=?"/>
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:
<http ... access-denied-page="/accessDenied.jsp">
...
</http>
Default Target URL: essa é a página que o usuário será redirecionado caso seja autenticado com sucesso no login:
<http ... >
...
<form-login ... default-target-url="/home.do"/>
...
</http>
Logout URL: essa é a página que o suusário será redirecionado quando fizer logout:
<http ... >
...
<logout logout-success-url="/home.do"/>
...
</http>
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:
<http ... >
...
<form-login ... authentication-failure-url="/login.jsp?login_error=1"/>
...
</http>
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!





[...] This post was mentioned on Twitter by Loiane Groner, igocoelho. igocoelho said: RT: @loiane: Blog post: Tutorial: Começando com Spring Security http://bit.ly/6z0myB [...]
Bom artigo, espero para ver a continuação
security-applicationContext.xml
applicationContext-security.xml
Loiane,
tem um erro no tutorial.
No web xml o nome do arquivo applicationContext-security está errado.
Thanks
Corrigido!
Valeu pela dica!
Estou esperando a continuação do tutorial, estou tenho problemas na implementação de um customAuthenticationManager e CustomAuthenticationProvider
vlw
olá Loiane,
Legal o post, eu so fiquei na duvida se a palavra ROLE de access é required, pq antes quando se usava realm em “role” poderia ser qualquer coisa “gerente” , “analista” e com o spring se fizer isso colocar em access=”Gerente” nao funciona, como contornar isso?
abracos,
Ei Camilo,
Sim, é necessário colocar o prefixo ROLE. Se no banco de dados vc não tiver o prefixo, pode fazer uma customização de classes do framework e concatenar o prefixo com o valor que veio do banco para ficar igual ao arquivo de configuração.
Infelizmente, se não tiver o prefixo, não vai funcionar.
[]‘s
olá loiane,
Pow isso eh muito ruim, pois como dar suporte a sistema legado que nao tem o prefix? até o realm é mais inteligente nesse aspecto, andei pesquisando, mas sem sucesso em solucionar o problema veja: http://forum.springsource.org/showthread.php?t=59296 http://forum.springsource.org/showthread.php?t=60644
vc ja passou por algo ? e como conseguiu fixar, as opcoes nos links acima, nao funcionaram aqui. Acho que falta algo que nao foi comentado no forum .
abracos, tks
@Camilo,
Bem, eu não acho isso um problema na verdade. Isso é muito fácil de resolver no lado servidor com uma concatenação de strings, sem mudar banco de dados nem nada. Sim, já passei por isso e foi assim que resolvi. Tive que implementar a parte de segurança de uma app usando spring security puro.
Você teria que ter a parte básica configurada para a configuração que foi mencionada no forum funcionar.
O framework Spring é um dos melhores que já vi, faz muito sucesso fora do Brasil, por ser um framework bem completo. A documentação não é das melhores, mas compensa. Aqui na empresa só usamos Spring, e implementaram um customização do spring secuirty que é utilizado em todas as aplicações java. E olha que segurança é o que ele mais olham nas app, por se tratar de informações extremamente confidenciais.
Dê uma estudada mais à fundo que vc vai acabar gostando!
olá Loiane,
Realmente concordo com vc, ja li sobre o framework e vi que realmente tem varios recursos que interessantes e bem implementados. já pesquisei de varias formas no google, mas sem sucesso, muitos posts, incompletos ou sem respostas para o mesmo problema que estou passando. E assim nao faz sentindo ele ter ROLE_ como prefix e default, assim os sistemas legados dar essa dor de cabeça, a ideia na minha opiniao era que deveria funcionar desde o sem ROLE_ para o ROLE_. tipo nessa app, em um form JSF, temos lá selectOneItem e ele carrega por exemplo dados que foram inserido no BD por outro usuario como : Gerente, Vendedor etc. e nao poderia carregar no form: ROLE_Gerente. nao faria sentindo isso.
Mas, obrigado pela ajuda, vou ver se consigo remover esse prefix. =/
abracos,
@Camilo,
Na verdade, não vi nenhum empecilho nesse exemplo que citou. Dê uma olhada como customizar o authentication. Você não precisa fazer nenhuma mudança no banco ou no arquivo de configuração, só no lado server.
Por exemplo, a sua configuração vai ficar como ROLE_GERENTE, no banco vai ficar apenas gerente, e na hora de fazer o login, você só vai precisa concatenar a String “ROLE_” com o que veio do banco no server.
Dê uma olhada nessa thread que tem explicando direitinho:
http://forum.springsource.org/showthread.php?t=65079
opa! Loiane,
Pois eh, vi ontem a solucao sem mexer algo no banco ficou assim: SELECT username, concat(‘ROLE_’,authority) FROM users where username = ?
hehe thanks girl
Você também pode configurar o bean RoleVoter para o prefixo de suas permissões.
Lembrando que você pode também criar grupos de usuários, e ao invés de associar o usuários as permissões, você associa as permissões ao grupo, e o(s) grupo(s) ao usuário.
Tudo bem loiane, eu estou com algumas duvidas sobre o como vc deixou os seus beans, vc poderia postar eles ?