Tutorial: Começando com Spring Security

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!

Posts Similares

Filed Under: Spring Security

Tags: , ,

About the Author

Bacharel em Ciência da Computação, trabalha como Senior Software Engineer/Tech Leader no Citibank (maior instituição financeira do mundo), em São Paulo. Autora do livro ExtJS 4 First Look, publicado mundialmente pela editora Packt Publishing. Possui um blog em inglês (http://loianegroner.com) e também contribui com artigos em inglês para o Java Lobby do portal DZone. JUG leader do CampinasJUG/Java Campinas, coordenadora do ESJUG e uma das fundadoras do JDuchessBR.

Comments (33)

Trackback URL | Comments RSS Feed

  1. [...] 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 [...]

  2. bruno cruz says:

    Bom artigo, espero para ver a continuação

    security-applicationContext.xml
    applicationContext-security.xml

  3. André Silva says:

    Loiane,

    tem um erro no tutorial.

    No web xml o nome do arquivo applicationContext-security está errado.

    Thanks :)

  4. Loiane says:

    Corrigido!
    Valeu pela dica! :)

  5. Bruno Cruz says:

    Estou esperando a continuação do tutorial, estou tenho problemas na implementação de um customAuthenticationManager e CustomAuthenticationProvider

    vlw

  6. camilo lopes says:

    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,

  7. Loiane says:

    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

  8. camilo lopes says:

    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

  9. Loiane says:

    @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! :)

  10. camilo lopes says:

    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,

  11. Loiane says:

    @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

  12. camilo lopes says:

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

  13. Sandokan says:

    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.

  14. Murilo says:

    Tudo bem loiane, eu estou com algumas duvidas sobre o como vc deixou os seus beans, vc poderia postar eles ?

  15. Guevara says:

    Olá Loiane! Legal seu tutorial.
    Estou com um probleminha, no footer.jsp coloquei:

    <a href="”>Login

    Olá
    <a href="”>Logout

    Mas o rodapé com o “Login” só aparece na Index, nas outras só aparece o rodapé sem o “Login”, sabe que pode ser?
    Abraço!

  16. [...] Tutorial: Começando com Spring Security [...]

  17. Loiane says:

    Olá Guevara,
    Você está incluindo o seu footer em todas as páginas?

  18. Olá,
    Gostaria de manter as URLs e os ROLEs (do intercept-url) em banco de dados. Não configurando cada URL/ROLEs via XML.
    Saberia como recuperar/configurar os ROLES e as URLs em banco de dados?

    Muito Obrigado.

  19. Douglas says:

    Olá..
    Estou tentando implementar o login em uma pequena app jsf 2 que fiz, porém, quando tento rodar, aparece o erro:
    java.lang.ClassNotFoundException: org.springframework.web.filter.DelegatingFilterProxy
    Adicionei as bibliotecas indicadas às bibliotecas do netbeans, no mesmo lugar que tenho driver jdbc, hibernate e skin do primefaces… só a versão que está diferente, baixei a 2.0.6.
    Ví que você já tinha o spring no resto do app, eu não tenho, devo adicionar alguma outra biblioteca?
    Obrigado.

  20. Loiane says:

    Olá Douglas,
    Vc precisa adicionar os outros jars do spring que estão no projeto, pois são dependência.
    []‘s

  21. juniorsatanas says:

    Loiane Boa tarde..
    Na minha APP não estou conseguindo fazer o Logout usando Spring security, como deve ficar o arquivo aplication e o jsp ?

  22. Loiane says:

    Olá Junior,

    Só precisa de colocar o link do logout na sua aplicação: http://www.loiane.com/2010/01/spring-security-login-e-logout-form-jsp/

  23. Junior says:

    Loiane

    quando eu adiciona as tags no meu web.xml

    springSecurityFilterChain
    /*

    as paginas da minha aplicação n ficam mais acessiveis, da aquele erro, HTTP Status 404, n sei o que fazer

  24. Loiane says:

    Olá Junior,
    Só olhando o config da aplicação e a estrutura da mesma pra falar qual é o erro.
    Você pode estar protegendo arquivos essenciais para visualização que deveriam fica públicos.
    Como por exemplo, a página de login não pode ter filtro, pois os ususário ainda não estão autenticados para fazer o filtro.

  25. Loiane says:

    Olá Alvaro,
    Demorei, mas descobri a resposta para a sua pergunta.
    Existem alguma opções:
    1 – Pode fazer através de Domain Object Security (ACLs): http://static.springsource.org/spring-security/site/docs/3.0.x/reference/domain-acls.html
    2 – Pode implementar uma subclasse do AbstractSecurityInterceptor: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/technical-overview.html#secure-objects

  26. Bma says:

    Galera, estou comecando agora com o spring, e a explicacao acima é boa mesmo, mas eu quero perceber a funcao de cada “coisa” que configuramos, por exemplo o listener, filtre, etc..
    Se pudesem me explicar ou indicar um tutorial que expliquem isso, agradeco.

    Eh urgente galera

    Obrigado

  27. Loiane says:

    Olá Bma,
    Sugiro que dê uma olhada na documentação que também explica cada tópico que citou.
    Obrigada!
    []‘s

  28. PAlfonso says:

    Olá!!! Apreciei bastante o conteúdo aqui apresentado. De muita qualidade e atenção com toda a comunidade Java. Todavia tenho uma dúvida que a princípio pode ser simples, mas não consegui ainda ver aderência de como fazer neste exemplo.

    Imaginemos que eu já tenho uma classe que faz a abstração da minha camada de autenticação, ou seja, valida se as credenciais fornecidas estão corretas ou não… e retorna no método um boolean pra informar sucesso ou falha no processo.

    Poderia usar esta classe como a ponte entre a página JSF e o SPRING SECURITY nos bastidores? Entendo que tudo não passa de user + password, apenas informando quem é o Authentication Provider que o SPRING SECURITY vai utilizar para validar a informação. A partir daí, ele mesmo está pronto para caminhar com as próprias pernas.

    Neste exemplo procuro simplificar apenas para ter a ROLE_ADMIN e ROLE_USER.

    Imagine que o set de páginas está disponível e que você deseja apenas implementar a segurança. Faz sentido esta idéia?

    Obrigado pela atenção!

  29. Legal o tutorial! Pelo Spring Security eu posso usar autenticação por outros meios como o oauth ou Google Connect!?

    O Grails tem umas facilidades com relação ao SS, mas a mesma facilidade atrapalha em fazer coisas diferentes do que o plugin permite… rs! Seu tutorial foi de grande ajuda nisso! :D

  30. Obi-Wan_Zure says:

    Olá, Loiane, boa tarde! Uma pergunta (mas acho que já sei a resposta). Pra utilizar o SpringSecurity é necessário que o projeto já esteja utilizando o Spring?

  31. Loiane says:

    Olá Obi-Wan_Zure,
    Não. Se vc usar Spring fica mais fácil de configurar, mas vc pode usar o Spring Security sozinho no projeto sem problemas.
    O Spring tem vários módulos, e esses módulos podem ser usados separadamente. O spring security é um deles e o spring mvc é um outro módulo.
    []‘s

  32. Loiane says:

    Olá Geek And Cook,
    Sim, é possível fazer essas integrações sim. Eu não tenho nenhum tutorial pronto sobre isso, mas na net vc acha alguns.
    []‘s

  33. Loiane says:

    Olá PAlfonso,
    Sim, é possível fazer isso sim.
    Vc precisa criar sua própria forma de autenticação, mas precisa retornar o que o Spring Security espera receber.
    Dê uma olhada na net como fazer a customização disso. Se não me engano, vc precisa criar apenas uma nova classe e e fazer a configuração no arquivo xml.
    []‘s

Leave a Reply




If you want a picture to show with your comment, go get a Gravatar.