Ejemplo de Tutorial de Spring Security

Spring Security proporciona formas de realizar autenticación y autorización en una aplicación web. Podemos usar Spring Security en cualquier aplicación web basada en servlets.

Spring Security

Algunos de los beneficios de usar Spring Security son:

  1. Tecnología probada, es mejor usar esto que reinventar la rueda. La seguridad es algo en lo que debemos tener un cuidado extra, de lo contrario, nuestra aplicación será vulnerable a ataques.
  2. Previene algunos de los ataques comunes como CSRF, ataques de fijación de sesión.
  3. Fácil de integrar en cualquier aplicación web. No necesitamos modificar las configuraciones de la aplicación web, Spring inyecta automáticamente filtros de seguridad en la aplicación web.
  4. Proporciona soporte para la autenticación de diferentes maneras: en memoria, DAO, JDBC, LDAP y muchas más.
  5. Ofrece la opción de ignorar patrones de URL específicos, útil para servir archivos HTML estáticos, imágenes, etc.
  6. Soporte para grupos y roles.

Ejemplo de Spring Security

Crearemos una aplicación web e integraremos Spring Security en ella. Cree una aplicación web utilizando la opción “Proyecto Web Dinámico” en Eclipse, para que nuestra aplicación web esqueleto esté lista. Asegúrate de convertirla en un proyecto Maven porque estamos utilizando Maven para la construcción y implementación. Si no estás familiarizado con estos pasos, consulta el Tutorial de Aplicaciones Web en Java. Una vez que tengamos nuestra aplicación segura, la estructura final del proyecto se verá como en la siguiente imagen. Examinaremos tres métodos de autenticación de seguridad de Spring.

  1. en memoria
  2. DAO
  3. JDBC

Para JDBC, estoy utilizando la base de datos MySQL y he ejecutado el siguiente script para crear las tablas de detalles de usuario.

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;

También necesitaríamos configurar JDBC DataSource como JNDI en nuestro contenedor de servlets, para aprender sobre esto por favor lee el Ejemplo de DataSource JNDI en Tomcat.

Dependencias de Maven de Spring Security

Aquí está nuestro archivo final pom.xml.

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

Tenemos las siguientes dependencias relacionadas con el Framework Spring.

  1. spring-jdbc: Se utiliza para operaciones JDBC mediante el método de autenticación JDBC. Requiere la configuración de DataSource como JNDI. Para un ejemplo completo de su uso, consulta Ejemplo de Spring DataSource JNDI
  2. spring-security-taglibs: Biblioteca de etiquetas de Spring Security, la he utilizado para mostrar los roles de usuario en la página JSP. En la mayoría de los casos, probablemente no lo necesitarás.
  3. spring-security-config: Se utiliza para configurar los proveedores de autenticación, ya sea para usar JDBC, DAO, LDAP, etc.
  4. spring-security-web: Este componente integra Spring Security con la API Servlet. Lo necesitamos para enchufar nuestra configuración de seguridad en la aplicación web.

También ten en cuenta que estaremos utilizando la característica Servlet API 3.0 para agregar listener y filtros de forma programática, por eso la versión de la API servlet en las dependencias debe ser 3.0 o superior.

Ejemplo de Páginas de Vista de Spring Security

Tenemos páginas JSP y HTML en nuestra aplicación. Queremos aplicar autenticación en todas las páginas excepto en las 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.

Ejemplo de Implementación de DAO de UserDetailsService en Spring Security

Ya que estaremos utilizando autenticación basada en DAO, también necesitamos implementar la interfaz UserDetailsService y proporcionar la implementación del método loadUserByUsername(). Idealmente deberíamos usar algún recurso para validar al usuario, pero por simplicidad estoy realizando una validación 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");
		}
		
		//creando detalles de usuario ficticios, deberíamos realizar operaciones 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;
			}
		};
	}

}

Observa que estoy creando una clase interna anónima de UserDetails y la estoy devolviendo. Puedes crear una clase de implementación para ello y luego instanciarla y devolverla. Normalmente, ese es el camino a seguir en aplicaciones reales.

Ejemplo de Implementación de WebSecurityConfigurer en Spring Security

Podemos implementar la interfaz WebSecurityConfigurer o podemos extender la clase de implementación base WebSecurityConfigurerAdapter y sobrescribir los 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 {

		// autenticación en memoria
		// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");

		// utilizando un DAO personalizado de UserDetailsService
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// utilizando 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()
                // Spring Security debe ignorar completamente las URL que terminan con .html
                .antMatchers("/*.html");
    }

}

Observa que estamos ignorando todos los archivos HTML anulando el método configure(WebSecurity web). El código muestra cómo conectar la autenticación JDBC. Necesitamos configurarlo proporcionando un DataSource. Dado que estamos utilizando tablas personalizadas, también debemos proporcionar las consultas de selección para obtener los detalles del usuario y sus roles. Configurar la autenticación en memoria y basada en DAO es fácil, están comentadas en el código anterior. Puedes descomentarlas para usarlas, asegúrate de tener solo una configuración a la vez. Se requieren las anotaciones @Configuration y @EnableWebSecurity, para que el framework de Spring sepa que esta clase se utilizará para la configuración de seguridad de Spring. La configuración de Spring Security utiliza el Patrón Builder y, según el método de autenticación, algunos de los métodos no estarán disponibles más adelante. Por ejemplo, auth.userDetailsService() devuelve la instancia de UserDetailsService y luego no podemos tener otras opciones, como no podemos establecer DataSource después de eso.

Integrando Spring Security Web con la API Servlet

La última parte consiste en integrar nuestra clase de configuración de Spring Security con la API Servlet. Esto se puede hacer fácilmente extendiendo la clase AbstractSecurityWebApplicationInitializer y pasando la clase de configuración de seguridad en el constructor de la superclase. 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);
    }
}

Cuando nuestro contexto se inicia, utiliza ServletContext para añadir el listener ContextLoaderListener y registrar nuestra clase de configuración como Servlet Filter. Ten en cuenta que esto solo funcionará en contenedores de servlets compatibles con Servlet-3. Así que, si estás utilizando Apache Tomcat, asegúrate de que su versión sea 7.0 o superior. Nuestro proyecto está listo, simplemente despliégalo en tu contenedor de servlets favorito. Yo estoy utilizando Apache Tomcat-7 para ejecutar esta aplicación. Las imágenes a continuación muestran la respuesta en varios escenarios.

Accediendo a la página HTML sin seguridad

Falló la autenticación por credenciales incorrectas

Página de inicio con autenticación JDBC de Spring Security

Página de inicio con autenticación DAO de Spring Security

Página de inicio con autenticación en memoria de Spring Security

Página de cierre de sesión

Si desea utilizar un contenedor Servlet que no admita las especificaciones Servlet 3, entonces necesitaría registrar DispatcherServlet a través del descriptor de despliegue. Consulte la documentación de JavaDoc de WebApplicationInitializer para obtener más detalles. Eso es todo para el tutorial de ejemplo de Spring Security y su integración en una aplicación web basada en Servlet. Descargue el proyecto de ejemplo desde el siguiente enlace y juegue con él para aprender más.

Descargar proyecto de seguridad de Servlet de Spring

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