Exemplo de Tutorial do Spring Security

Spring Security fornece maneiras de realizar autenticação e autorização em uma aplicação web. Podemos usar o Spring Security em qualquer aplicação web baseada em servlet.

Alguns dos benefícios de usar o Spring Security são:

  1. Tecnologia comprovada, é melhor usar isso do que reinventar a roda. A segurança é algo em que precisamos ter cuidado extra, caso contrário, nossa aplicação ficará vulnerável a ataques.
  2. Previne alguns dos ataques comuns, como CSRF, ataques de fixação de sessão.
  3. Fácil de integrar em qualquer aplicação web. Não precisamos modificar as configurações da aplicação web, o Spring automaticamente injeta filtros de segurança na aplicação web.
  4. Fornece suporte para autenticação de diferentes maneiras – em memória, DAO, JDBC, LDAP e muitas outras.
  5. Oferece a opção de ignorar padrões de URL específicos, bom para servir arquivos HTML e de imagem estáticos.
  6. Suporte para grupos e papéis.

Exemplo de Spring Security

Vamos criar uma aplicação web e integrá-la com o Spring Security. Crie uma aplicação web usando a opção “Projeto Web Dinâmico” no Eclipse, para que nossa aplicação web esquelética esteja pronta. Certifique-se de convertê-la em um projeto Maven, pois estamos usando o Maven para construção e implantação. Se você não estiver familiarizado com essas etapas, consulte o Tutorial de Aplicação Web Java. Assim que tivermos nossa aplicação segura, a estrutura final do projeto parecerá com a imagem abaixo. Vamos analisar três métodos de autenticação de segurança do Spring.

  1. em memória
  2. DAO
  3. JDBC

Para JDBC, estou usando 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 JDBC como JNDI em nosso contêiner de servlets, para saber mais sobre isso, por favor, leia o Exemplo de DataSource JNDI do Tomcat.

Dependências do Maven do 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.

  1. 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 JNDI do Spring DataSource
  2. 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.
  3. spring-security-config: É usado para configurar os provedores de autenticação, seja JDBC, DAO, LDAP etc.
  4. 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 usaremos a funcionalidade do Servlet API 3.0 para adicionar listener e filtros programaticamente, por isso a versão da API Servlet nas dependências deve ser 3.0 ou superior.

Páginas de visualização de exemplo 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 do Spring Security UserDetailsService

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 simplicidade, estou 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 do usuário fictício, deve 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 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 então instanciá-la e retorná-la. Geralmente, é assim que é feito 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 sobrescrever os métodos. 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 custom UserDetailsService DAO
		// 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 os 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 implementar a autenticação JDBC. Precisamos configurá-lo fornecendo um 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, mas 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 utiliza o Padrão Builder e, com base no método de autenticação, alguns métodos não estarão disponíveis posteriormente. Por exemplo, auth.userDetailsService() retorna a instância de UserDetailsService e, em seguida, não podemos ter outras opções, como não podemos definir um DataSource após isso.

Integrando Spring Security Web com a API Servlet

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 é inicializado, ele usa o ServletContext para adicionar o listener ContextLoaderListener e registrar nossa classe de configuração como Filtro Servlet. Observe que isso funcionará apenas em contêineres de servlets compatíveis com 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 de servlet favorito. Estou usando o Apache Tomcat-7 para executar esta aplicação. As imagens abaixo mostram a resposta em vários cenários.

Acessando a Página HTML sem Segurança

Autenticação Falhou para Credenciais Incorretas

Página Inicial com Autenticação JDBC do Spring Security

Página Inicial com Autenticação DAO do Spring Security

Página Inicial com Autenticação em Memória do Spring Security

Página de Logout

Se você deseja usar um Contêiner Servlet que não suporta as especificações do Servlet 3, será necessário registrar o DispatcherServlet por meio do descritor de implantação. Consulte o JavaDoc do WebApplicationInitializer para obter mais detalhes. Isso é tudo para o exemplo e integração do Spring Security em uma Aplicação Web Baseada em Servlet. Por favor, faça o download do projeto de exemplo no link abaixo e experimente para aprender mais.

Download do Projeto de Segurança do Servlet do Spring

Source:
https://www.digitalocean.com/community/tutorials/spring-security-example-tutorial