A Spring Security oferece maneiras de realizar autenticação e autorização em uma aplicação web. Podemos utilizar o Spring Security em qualquer aplicação web baseada em servlet.
Spring Security
Alguns dos benefícios de usar o Spring Security são:
- Tecnologia comprovada, é melhor usar isso do que reinventar a roda. A segurança é algo que precisamos cuidar extra, caso contrário, nossa aplicação ficará vulnerável a ataques.
- Previne alguns dos ataques comuns, como CSRF e ataques de fixação de sessão.
- Fácil de integrar em qualquer aplicação web. Não precisamos modificar as configurações da aplicação web, o Spring injeta automaticamente filtros de segurança na aplicação web.
- Fornece suporte para autenticação de diferentes maneiras – em memória, DAO, JDBC, LDAP e muitas outras.
- Oferece opção para ignorar padrões de URL específicos, útil para servir arquivos HTML e de imagem estáticos.
- Suporte para grupos e funções.
Exemplo de Spring Security
Vamos criar uma aplicação web e integrá-la ao Spring Security. Crie uma aplicação web usando a opção “Dynamic Web Project” no Eclipse, para que nossa estrutura básica da aplicação web esteja pronta. Certifique-se de convertê-la em um projeto Maven, pois estamos utilizando o Maven para compilação e implantação. Se não estiver familiarizado com essas etapas, consulte o Java Web Application Tutorial. Uma vez que nossa aplicação estiver segura, a estrutura final do projeto será semelhante à imagem abaixo. Vamos explorar três métodos de autenticação no Spring Security.
- in-memory
- DAO
- JDBC
Para o JDBC, estou utilizando o banco de dados MySQL e executei o seguinte script para criar as tabelas de detalhes do usuário.
CREATE TABLE `Employees` (
`username` varchar(20) NOT NULL DEFAULT '',
`password` varchar(20) NOT NULL DEFAULT '',
`enabled` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Roles` (
`username` varchar(20) NOT NULL DEFAULT '',
`role` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`username`,`role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Employees` (`username`, `password`, `enabled`)
VALUES
('pankaj', 'pankaj123', 1);
INSERT INTO `Roles` (`username`, `role`)
VALUES
('pankaj', 'Admin'),
('pankaj', 'CEO');
commit;
Também precisaremos configurar o DataSource do JDBC como JNDI em nosso contêiner de servlet. Para aprender mais sobre isso, por favor, leia Tomcat JNDI DataSource Example.
Dependências do Maven para o Spring Security
Aqui está nosso arquivo pom.xml final.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>WebappSpringSecurity</groupId>
<artifactId>WebappSpringSecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- Spring Security Artifacts - START -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<!-- Spring Security Artifacts - END -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Temos as seguintes dependências relacionadas ao Spring Framework.
- spring-jdbc: Isso é usado para operações JDBC pelo método de autenticação JDBC. Requer configuração de DataSource como JNDI. Para um exemplo completo de uso, consulte Exemplo de Spring DataSource JNDI
- spring-security-taglibs: Biblioteca de tags do Spring Security, eu a usei para exibir as funções do usuário na página JSP. Na maioria das vezes, você não precisará dela.
- spring-security-config: É usado para configurar os provedores de autenticação, seja JDBC, DAO, LDAP, etc.
- spring-security-web: Este componente integra o Spring Security à API Servlet. Precisamos dele para conectar nossa configuração de segurança na aplicação web.
Também observe que estaremos usando a funcionalidade da API Servlet 3.0 para adicionar listeners e filtros programaticamente, por isso a versão da API Servlet nas dependências deve ser 3.0 ou superior.
Páginas de Exemplo de Visualização do Spring Security
Temos páginas JSP e HTML em nossa aplicação. Queremos aplicar autenticação em todas as páginas, exceto nas páginas HTML. health.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Health Check</title>
</head>
<body>
<h3>Service is up and running!!</h3>
</body>
</html>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="https://www.springframework.org/security/tags" prefix="sec" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Home Page</title>
</head>
<body>
<h3>Home Page</h3>
<p>
Hello <b><c:out value="${pageContext.request.remoteUser}"/></b><br>
Roles: <b><sec:authentication property="principal.authorities" /></b>
</p>
<form action="logout" method="post">
<input type="submit" value="Logout" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
</body>
</html>
I have included index.jsp
as welcome-file
in the application deployment descriptor. Spring Security takes care of CSRF attack, so when we are submitting form for logout, we are sending the CSRF token back to server to delete it. The CSRF object set by Spring Security component is _csrf and we are using it’s property name and token value to pass along in the logout request. Let’s look at the Spring Security configurations now.
Exemplo de Implementação do DAO UserDetailsService do Spring Security
Como estaremos usando autenticação baseada em DAO, precisamos implementar a interface UserDetailsService
e fornecer a implementação para o método loadUserByUsername()
. Idealmente, deveríamos estar usando algum recurso para validar o usuário, mas para simplificar, estou apenas fazendo uma validação básica. AppUserDetailsServiceDAO.java
package com.journaldev.webapp.spring.dao;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class AppUserDetailsServiceDAO implements UserDetailsService {
protected final Log logger = LogFactory.getLog(getClass());
@Override
public UserDetails loadUserByUsername(final String username)
throws UsernameNotFoundException {
logger.info("loadUserByUsername username="+username);
if(!username.equals("pankaj")){
throw new UsernameNotFoundException(username + " not found");
}
// criando detalhes de usuário fictícios, deveríamos realizar operações JDBC
return new UserDetails() {
private static final long serialVersionUID = 2059202961588104658L;
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return "pankaj123";
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
List auths = new java.util.ArrayList();
auths.add(new SimpleGrantedAuthority("admin"));
return auths;
}
};
}
}
Observe que estou criando uma classe interna anônima de UserDetails
e a retornando. Você pode criar uma classe de implementação para isso e depois instanciá-la e retorná-la. Normalmente, esse é o caminho a seguir em aplicações reais.
Exemplo de Implementação do WebSecurityConfigurer do Spring Security
Podemos implementar a interface WebSecurityConfigurer
ou podemos estender a classe de implementação base WebSecurityConfigurerAdapter
e substituir os métodos necessários. SecurityConfig.java
package com.journaldev.webapp.spring.security;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import com.journaldev.webapp.spring.dao.AppUserDetailsServiceDAO;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
// autenticação em memória
// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");
// usando DAO customizado UserDetailsService
// auth.userDetailsService(new AppUserDetailsServiceDAO());
// usando JDBC
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx
.lookup("java:/comp/env/jdbc/MyLocalDB");
final String findUserQuery = "select username,password,enabled "
+ "from Employees " + "where username = ?";
final String findRoles = "select username,role " + "from Roles "
+ "where username = ?";
auth.jdbcAuthentication().dataSource(ds)
.usersByUsernameQuery(findUserQuery)
.authoritiesByUsernameQuery(findRoles);
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
// O Spring Security deve ignorar completamente URLs que terminam com .html
.antMatchers("/*.html");
}
}
Observe que estamos ignorando todos os arquivos HTML substituindo o método configure(WebSecurity web)
. O código mostra como conectar a autenticação JDBC. Precisamos configurá-lo fornecendo DataSource. Como estamos usando tabelas personalizadas, também é necessário fornecer as consultas de seleção para obter os detalhes do usuário e suas funções. Configurar a autenticação em memória e baseada em DAO é fácil, elas estão comentadas no código acima. Você pode descomentá-las para usá-las, certifique-se de ter apenas uma configuração por vez. As anotações @Configuration
e @EnableWebSecurity
são necessárias, para que o framework Spring saiba que esta classe será usada para a configuração de segurança do Spring. A Configuração de Segurança do Spring está usando o Padrão Builder e com base no método de autenticação, alguns dos métodos não estarão disponíveis mais tarde. Por exemplo, auth.userDetailsService()
retorna a instância de UserDetailsService
e então não podemos ter outras opções, como não podemos definir DataSource depois disso.
Integrando Spring Security Web com Servlet API
A última parte é integrar nossa classe de configuração do Spring Security à API Servlet. Isso pode ser feito facilmente estendendo a classe AbstractSecurityWebApplicationInitializer
e passando a classe de configuração de segurança no construtor da superclasse. SecurityWebApplicationInitializer.java
package com.journaldev.webapp.spring.security;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends
AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(SecurityConfig.class);
}
}
Quando nosso contexto é iniciado, ele utiliza o ServletContext para adicionar o ouvinte ContextLoaderListener e registrar nossa classe de configuração como Filtro Servlet. Observe que isso funcionará apenas em contêineres servlet compatíveis com o Servlet-3. Portanto, se estiver usando o Apache Tomcat, certifique-se de que sua versão seja 7.0 ou superior. Nosso projeto está pronto, basta implantá-lo em seu contêiner servlet favorito. Estou usando o Apache Tomcat-7 para executar esta aplicação. Abaixo, as imagens mostram as respostas em vários cenários.
Acessando a página HTML sem segurança
Falha na autenticação devido a credenciais inválidas
Página inicial com autenticação JDBC do Spring Security
Página Inicial com Autenticação DAO do Spring Security UserDetailsService
Página Inicial com Autenticação em Memória do Spring Security
Página de Logout
Se você deseja usar um Container Servlet que não suporta Servlet Specs 3, então você precisaria registrar o
DispatcherServlet
por meio do descritor de implantação. Consulte o JavaDoc do WebApplicationInitializer
para mais detalhes. Isso é tudo para o exemplo de tutorial do Spring Security e sua integração em uma Aplicação Web Baseada em Servlets. Por favor, faça o download do projeto de exemplo a partir do link abaixo e explore-o para aprender mais.
Source:
https://www.digitalocean.com/community/tutorials/spring-security-example-tutorial