Esempio di tutorial sulla sicurezza di Spring

Spring Security fornisce modi per eseguire l’autenticazione e l’autorizzazione in un’applicazione web. Possiamo utilizzare Spring Security in qualsiasi applicazione web basata su servlet.

Spring Security

Alcuni dei vantaggi nell’utilizzare Spring Security sono:

  1. Tecnologia comprovata, è meglio utilizzare questa piuttosto che reinventare la ruota. La sicurezza è qualcosa a cui dobbiamo prestare particolare attenzione, altrimenti la nostra applicazione sarà vulnerabile agli attacchi.
  2. Previene alcuni degli attacchi comuni come CSRF, attacchi di fissazione della sessione.
  3. Facile da integrare in qualsiasi applicazione web. Non è necessario modificare le configurazioni dell’applicazione web, Spring inserisce automaticamente filtri di sicurezza nell’applicazione web.
  4. Fornisce supporto per l’autenticazione in diversi modi: in memoria, DAO, JDBC, LDAP e molti altri.
  5. Fornisce l’opzione di ignorare determinati modelli di URL, utile per servire file HTML e immagini statiche.
  6. Supporto per gruppi e ruoli.

Esempio di Spring Security

Creeremo un’applicazione web e la integreremo con Spring Security. Crea un’applicazione web utilizzando l’opzione “Dynamic Web Project” in Eclipse, in modo che la nostra struttura di base dell’applicazione web sia pronta. Assicurati di convertirla in un progetto Maven perché stiamo utilizzando Maven per la build e il deployment. Se non sei familiare con questi passaggi, consulta il Tutorial sull’Applicazione Web Java. Una volta che avremo sicurato la nostra applicazione, la struttura finale del progetto sarà simile all’immagine qui sotto. Esamineremo tre metodi di autenticazione di Spring Security.

  1. in-memory
  2. DAO
  3. JDBC

Per JDBC, sto utilizzando il database MySQL e ho eseguito lo script seguente per creare le tabelle dei dettagli degli utenti.

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;

Dovremmo anche configurare JDBC DataSource come JNDI nel nostro contenitore servlet, per saperne di più su questo si prega di leggere Esempio di Tomcat JNDI DataSource.

Dipendenze Maven di Spring Security

Ecco il nostro file pom.xml finale.

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

Abbiamo le seguenti dipendenze relative al framework Spring.

  1. spring-jdbc: Questo viene utilizzato per le operazioni JDBC tramite il metodo di autenticazione JDBC. Richiede una configurazione del DataSource come JNDI. Per un esempio completo del suo utilizzo, fare riferimento a Esempio di DataSource JNDI di Spring
  2. spring-security-taglibs: Libreria dei tag di Spring Security, l’ho utilizzata per visualizzare i ruoli degli utenti nella pagina JSP. Nella maggior parte dei casi, non ne avrai bisogno.
  3. spring-security-config: Viene utilizzato per configurare i provider di autenticazione, se utilizzare JDBC, DAO, LDAP, ecc.
  4. spring-security-web: Questo componente integra la sicurezza di Spring nell’API Servlet. Ne abbiamo bisogno per inserire la nostra configurazione di sicurezza nell’applicazione web.

Nota anche che utilizzeremo la funzionalità API Servlet 3.0 per aggiungere listener e filtri tramite programmazione, quindi la versione dell’API Servlet nelle dipendenze dovrebbe essere 3.0 o superiore.

Pagine di visualizzazione degli esempi di Spring Security

Abbiamo pagine JSP e HTML nella nostra applicazione. Vogliamo applicare l’autenticazione a tutte le pagine tranne le pagine 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.

Implementazione dell’esempio di UserDetailsService DAO di Spring Security

Poiché useremo anche l’autenticazione basata su DAO, è necessario implementare l’interfaccia UserDetailsService e fornire l’implementazione del metodo loadUserByUsername(). Idealmente dovremmo utilizzare una risorsa per convalidare l’utente, ma per semplicità sto facendo solo una convalida di base. 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");
		}
		
		//creazione dei dettagli utente fittizi, dovrebbe eseguire operazioni 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;
			}
		};
	}

}

Nota che sto creando una classe interna anonima di UserDetails e la restituisco. Puoi creare una classe di implementazione per essa e quindi istanziarla e restituirla. Di solito è il modo migliore in applicazioni reali.

Implementazione dell’esempio di WebSecurityConfigurer di Spring Security

Possiamo implementare l’interfaccia WebSecurityConfigurer o possiamo estendere la classe di implementazione di base WebSecurityConfigurerAdapter e sovrascrivere i metodi. 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 {

		// autenticazione in memoria
		// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");

		// utilizzando un DAO personalizzato UserDetailsService
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// utilizzando 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 dovrebbe ignorare completamente gli URL che terminano con .html
                .antMatchers("/*.html");
    }

}

Notare che stiamo ignorando tutti i file HTML sovrascrivendo il metodo configure(WebSecurity web). Il codice mostra come collegare l’autenticazione JDBC. Dobbiamo configurarlo fornendo il DataSource. Poiché stiamo utilizzando tabelle personalizzate, è necessario anche fornire le query di selezione per ottenere i dettagli dell’utente e i suoi ruoli. La configurazione dell’autenticazione in memoria e basata su DAO è semplice, sono commentate nel codice precedente. Puoi rimuovere i commenti per utilizzarle, assicurati di avere una sola configurazione alla volta. Le annotazioni @Configuration e @EnableWebSecurity sono richieste, in modo che il framework Spring sappia che questa classe sarà utilizzata per la configurazione della sicurezza di Spring. La configurazione della sicurezza di Spring utilizza il Pattern Builder e in base al metodo authenticate, alcuni metodi non saranno disponibili successivamente. Ad esempio, auth.userDetailsService() restituisce l’istanza di UserDetailsService e quindi non possiamo avere altre opzioni, come ad esempio non possiamo impostare il DataSource dopo di essa.

Integrare Spring Security Web con l’API Servlet

La parte finale consiste nell’integrare la nostra classe di configurazione di Spring Security con l’API Servlet. Questo può essere fatto facilmente estendendo la classe AbstractSecurityWebApplicationInitializer e passando la classe di configurazione di sicurezza al costruttore della 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 il nostro contesto si avvia, utilizza ServletContext per aggiungere il listener ContextLoaderListener e registrare la nostra classe di configurazione come Servlet Filter. Nota che questo funzionerà solo nei contenitori di servlet conformi a Servlet-3. Quindi, se stai utilizzando Apache Tomcat, assicurati che la versione sia 7.0 o superiore. Il nostro progetto è pronto, basta distribuirlo nel tuo contenitore servlet preferito. Sto utilizzando Apache Tomcat-7 per eseguire questa applicazione. Le immagini seguenti mostrano la risposta in vari scenari.

Accesso alla pagina HTML senza sicurezza

Autenticazione fallita per credenziali non valide

Pagina principale con autenticazione JDBC di Spring Security

Pagina principale con autenticazione DAO di Spring Security UserDetailsService

Pagina principale con autenticazione in memoria di Spring Security

Pagina di logout

Se si desidera utilizzare un contenitore servlet che non supporta le specifiche Servlet 3, sarà necessario registrare DispatcherServlet tramite il descrittore di distribuzione. Consultare il JavaDoc di WebApplicationInitializer per ulteriori dettagli. Questo è tutto per l’esempio di tutorial di Spring Security e la sua integrazione nell’applicazione Web basata su Servlet. Si prega di scaricare il progetto di esempio dal link sottostante e sperimentare con esso per saperne di più.

Scarica il progetto Spring Servlet Security

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