Voorbeeld Tutorial van Spring Security

Spring Security biedt manieren om authenticatie en autorisatie uit te voeren in een webtoepassing. We kunnen Spring Security gebruiken in elke op servlet gebaseerde webtoepassing.

Spring Security

Enkele voordelen van het gebruik van Spring Security zijn:

  1. Bewezen technologie, het is beter om dit te gebruiken dan het wiel opnieuw uit te vinden. Beveiliging is iets waar we extra voorzichtig mee moeten zijn, anders is onze toepassing kwetsbaar voor aanvallers.
  2. Voorkomt enkele veelvoorkomende aanvallen zoals CSRF en sessiefixatieaanvallen.
  3. Gemakkelijk te integreren in elke webtoepassing. We hoeven webtoepassingsconfiguraties niet te wijzigen, Spring injecteert automatisch beveiligingsfilters in de webtoepassing.
  4. Biedt ondersteuning voor authenticatie op verschillende manieren – in-memory, DAO, JDBC, LDAP en nog veel meer.
  5. Biedt de mogelijkheid om specifieke URL-patronen te negeren, handig voor het serveren van statische HTML- en afbeeldingsbestanden.
  6. Ondersteuning voor groepen en rollen.

Voorbeeld van Spring Security

We zullen een webapplicatie creëren en deze integreren met Spring Security. Maak een webapplicatie met de “Dynamic Web Project” optie in Eclipse, zodat onze skeletwebapplicatie gereed is. Zorg ervoor dat je het converteert naar een Maven-project, omdat we Maven gebruiken voor het bouwen en implementeren. Als je niet bekend bent met deze stappen, raadpleeg dan Java Web Application Tutorial. Zodra onze applicatie beveiligd is, zal de uiteindelijke projectstructuur eruitzien zoals de onderstaande afbeelding. We zullen drie authenticatiemethoden van Spring Security bekijken.

  1. in-memory
  2. DAO
  3. JDBC

Voor JDBC gebruik ik een MySQL-database en heb ik het volgende script uitgevoerd om de tabel met gebruikersgegevens aan te maken.

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;

We moeten ook JDBC DataSource configureren als JNDI in onze servlet-container. Voor meer informatie hierover kun je Tomcat JNDI DataSource Example lezen.

Spring Security Maven Dependencies

Hier is ons definitieve pom.xml-bestand.

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

We hebben de volgende afhankelijkheden met betrekking tot het Spring Framework.

  1. spring-jdbc: Dit wordt gebruikt voor JDBC-operaties door de JDBC-authenticatiemethode. Het vereist DataSource-setup als JNDI. Voor een volledig voorbeeld van het gebruik ervan, raadpleeg Spring DataSource JNDI Voorbeeld
  2. spring-security-taglibs: Spring Security-tagbibliotheek, ik heb het gebruikt om gebruikersrollen weer te geven op de JSP-pagina. De meeste keren zul je het echter niet nodig hebben.
  3. spring-security-config: Het wordt gebruikt voor het configureren van de authenticatieproviders, ofwel JDBC, DAO, LDAP, enz.
  4. spring-security-web: Dit component integreert de Spring Security met de Servlet API. We hebben het nodig om onze beveiligingsconfiguratie in een webtoepassing te pluggen.

Merk ook op dat we Servlet API 3.0-functie zullen gebruiken om luisteraars en filters programmatisch toe te voegen, daarom moet de servlet-api-versie in de afhankelijkheden 3.0 of hoger zijn.

Spring Security Voorbeeld Bekijk Pagina’s

We hebben JSP- en HTML-pagina’s in onze toepassing. We willen authenticatie toepassen op alle pagina’s behalve HTML-pagina’s. 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.

Voorbeeld van Spring Security UserDetailsService DAO-implementatie

Aangezien we ook op DAO gebaseerde authenticatie zullen gebruiken, moeten we de interface UserDetailsService implementeren en de implementatie voor de methode loadUserByUsername() verstrekken. Idealiter zouden we een bron moeten gebruiken om de gebruiker te valideren, maar voor de eenvoud voer ik gewoon basisvalidatie uit. 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");
		}
		
		//dummy gebruikersgegevens maken, zouden JDBC-operaties moeten uitvoeren
		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;
			}
		};
	}

}

Merk op dat ik een anonieme innerlijke klasse van UserDetails creëer en deze retourneer. Je kunt een implementatieklasse ervoor maken en deze vervolgens instantiëren en retourneren. Normaal gesproken is dat de manier om te gaan in daadwerkelijke toepassingen.

Voorbeeld van Spring Security WebSecurityConfigurer-implementatie

We kunnen de interface WebSecurityConfigurer implementeren of we kunnen de basisimplementatieklasse WebSecurityConfigurerAdapter uitbreiden en de methoden overschrijven. 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 {

		// in-memory authenticatie
		// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");

		// gebruik van aangepaste UserDetailsService DAO
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// gebruik van 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 moet volledig URLs negeren die eindigen op .html
                .antMatchers("/*.html");
    }

}

Let op dat we alle HTML-bestanden negeren door de configure(WebSecurity web)-methode te overschrijven. De code toont hoe JDBC-authenticatie kan worden ingeplugd. We moeten het configureren door een DataSource te verstrekken. Aangezien we aangepaste tabellen gebruiken, moeten we ook de selectiequery’s verstrekken om de gebruikersgegevens en hun rollen op te halen. Het configureren van in-memory en DAO-gebaseerde authenticatie is eenvoudig, ze zijn hierboven uitgeschakeld. U kunt ze uncommenten om ze te gebruiken, zorg ervoor dat u slechts één configuratie tegelijk hebt. @Configuration en @EnableWebSecurity-annotaties zijn vereist, zodat het Spring-framework weet dat deze klasse zal worden gebruikt voor de configuratie van Spring Security. De configuratie van Spring Security maakt gebruik van Builder Pattern en op basis van de authenticate-methode zullen sommige methoden later niet beschikbaar zijn. Bijvoorbeeld, auth.userDetailsService() retourneert de instantie van UserDetailsService en daarna kunnen we geen andere opties hebben, zoals het instellen van DataSource na het oproepen ervan.

Het integreren van Spring Security Web met de Servlet API

Het laatste gedeelte is het integreren van onze Spring Security configuratieklasse met de Servlet API. Dit kan eenvoudig worden gedaan door de klasse AbstractSecurityWebApplicationInitializer uit te breiden en de configuratieklasse voor beveiliging door te geven aan de constructor van de superklasse. 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);
    }
}

Wanneer onze context wordt gestart, gebruikt het ServletContext om de ContextLoaderListener luisteraar toe te voegen en onze configuratieklasse te registreren als Servlet Filter. Let op dat dit alleen werkt in servletcontainers die voldoen aan Servlet-3. Dus als je Apache Tomcat gebruikt, zorg er dan voor dat de versie 7.0 of hoger is. Ons project is klaar, implementeer het gewoon in je favoriete servletcontainer. Ik gebruik Apache Tomcat-7 om deze toepassing uit te voeren. Hieronder tonen afbeeldingen de respons in verschillende scenario’s.

Toegang tot HTML-pagina zonder beveiliging

Authenticatiefout voor ongeldige referenties

Startpagina met Spring Security JDBC-authenticatie

Homepage met Spring Security UserDetailsService DAO-authenticatie

Homepage met in-memory authenticatie van Spring Security

Uitlogpagina

Als je een Servlet-container wilt gebruiken die Servlet-specificaties 3 niet ondersteunt, moet je DispatcherServlet registreren via het implementatiebeschrijvingsbestand. Raadpleeg de JavaDoc van WebApplicationInitializer voor meer details. Dat is alles voor het voorbeeld van Spring Security en de integratie ervan in op Servlets gebaseerde webapplicaties. Download het voorbeeldproject via onderstaande link en experimenteer ermee om meer te leren.

Download Spring Servlet Security Project

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