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:
- 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.
- Previene algunos de los ataques comunes como CSRF, ataques de fijación de sesión.
- 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.
- Proporciona soporte para la autenticación de diferentes maneras: en memoria, DAO, JDBC, LDAP y muchas más.
- Ofrece la opción de ignorar patrones de URL específicos, útil para servir archivos HTML estáticos, imágenes, etc.
- 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.
- en memoria
- DAO
- 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.
- 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
- 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.
- spring-security-config: Se utiliza para configurar los proveedores de autenticación, ya sea para usar JDBC, DAO, LDAP, etc.
- 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 extends GrantedAuthority> 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.
Source:
https://www.digitalocean.com/community/tutorials/spring-security-example-tutorial