Пример учебника по Spring Security

Spring Security предоставляет способы выполнения аутентификации и авторизации в веб-приложении. Мы можем использовать Spring Security в любом веб-приложении на основе сервлетов.

Spring Security

Некоторые из преимуществ использования Spring Security:

  1. Проверенная технология, лучше использовать ее, чем изобретать велосипед. Безопасность – это то, где нам нужно уделять особое внимание, иначе наше приложение будет уязвимо для атакующих.
  2. Предотвращает некоторые распространенные атаки, такие как CSRF, атаки фиксации сеанса.
  3. Легко интегрируется в любое веб-приложение. Нам не нужно изменять конфигурации веб-приложения, Spring автоматически внедряет фильтры безопасности в веб-приложение.
  4. Предоставляет поддержку аутентификации различными способами – в памяти, через DAO, JDBC, LDAP и многие другие.
  5. Позволяет игнорировать конкретные шаблоны URL, что полезно для обслуживания статических HTML-файлов, файлов изображений.
  6. Поддержка групп и ролей.

Пример Spring Security

Мы создадим веб-приложение и интегрируем его с Spring Security. Создайте веб-приложение, используя опцию “Dynamic Web Project” в Eclipse, чтобы наше веб-приложение было готово. Обязательно преобразуйте его в проект Maven, потому что мы используем Maven для сборки и развертывания. Если вы не знакомы с этими шагами, обратитесь к Руководству по созданию веб-приложений на Java. Как только наше приложение будет защищено, конечная структура проекта будет выглядеть как на изображении ниже. Мы рассмотрим три метода аутентификации в Spring Security.

  1. в памяти
  2. DAO
  3. JDBC

Для JDBC я использую базу данных MySQL и выполняю следующий сценарий для создания таблицы с данными пользователя.

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;

Нам также потребуется настроить JDBC DataSource как JNDI в нашем контейнере сервлетов. Чтобы узнать об этом, пожалуйста, прочитайте Пример Tomcat JNDI DataSource.

Зависимости Maven Spring Security

Вот наш файл pom.xml. У нас есть следующие зависимости, связанные с фреймворком Spring.

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

  1. spring-jdbc: Это используется для операций JDBC с помощью метода аутентификации JDBC. Для его использования требуется настройка источника данных как JNDI. Для полного примера его использования обратитесь к Примеру использования Spring DataSource JNDI.

  2. spring-security-taglibs: Библиотека тегов Spring Security, я использовал ее для отображения ролей пользователей на странице JSP. В большинстве случаев вам это не понадобится.

  3. spring-security-config: Используется для настройки провайдеров аутентификации, будь то JDBC, DAO, LDAP и т. д.

  4. spring-security-web: Этот компонент интегрирует Spring Security в API сервлетов. Нам нужно это для подключения нашей конфигурации безопасности в веб-приложение.

Также обратите внимание, что мы будем использовать функцию API сервлетов 3.0 для добавления слушателей и фильтров программно, поэтому версия API сервлетов в зависимостях должна быть 3.0 или выше.

Пример страниц просмотра Spring Security

У нас есть страницы JSP и HTML в нашем приложении. Мы хотим применить аутентификацию на всех страницах, кроме 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.

Пример использования Spring Security UserDetailsService DAO

Поскольку мы будем использовать аутентификацию на основе DAO, нам нужно реализовать интерфейс UserDetailsService и предоставить реализацию метода loadUserByUsername(). Идеально использовать некоторый ресурс для проверки пользователя, но для простоты я просто выполняю базовую проверку. 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");
		}
		
		// создание заглушки пользовательских данных, должны выполняться операции 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;
			}
		};
	}

}

Обратите внимание, что я создаю анонимный внутренний класс UserDetails и возвращаю его. Вы можете создать для этого класс реализации, а затем создать экземпляр и вернуть его. Обычно это делается в реальных приложениях.

Пример реализации WebSecurityConfigurer в Spring Security

Мы можем реализовать интерфейс WebSecurityConfigurer или расширить базовый класс реализации WebSecurityConfigurerAdapter и переопределить методы. 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 {

		// Аутентификация в памяти
		// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");

		// с использованием собственного пользовательского сервиса данных (UserDetailsService DAO)
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// с использованием 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 должен полностью игнорировать URL, заканчивающиеся на .html
                .antMatchers("/*.html");
    }

}

Обратите внимание, что мы игнорируем все файлы HTML, переопределяя метод configure(WebSecurity web). В коде показано, как подключить аутентификацию JDBC. Мы должны настроить ее, предоставив источник данных. Поскольку мы используем пользовательские таблицы, нам также необходимо предоставить запросы на выборку для получения данных пользователя и его ролей. Настройка аутентификации в памяти и на основе DAO проста, они закомментированы в вышеуказанном коде. Вы можете раскомментировать их для использования, убедитесь, что у вас есть только одна конфигурация за раз. Аннотации @Configuration и @EnableWebSecurity обязательны, чтобы фреймворк Spring знал, что этот класс будет использоваться для конфигурации безопасности Spring. Конфигурация безопасности Spring использует паттерн Builder и на основе метода аутентификации некоторые методы не будут доступны позже. Например, auth.userDetailsService() возвращает экземпляр UserDetailsService, и затем мы не можем использовать другие параметры, такие как нельзя установить источник данных после этого.

Интеграция Spring Security Web с Servlet API

Последний этап – интеграция нашего класса конфигурации Spring Security с Servlet API. Это можно сделать легко, расширив класс AbstractSecurityWebApplicationInitializer и передавая класс конфигурации безопасности в конструктор суперкласса. 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);
    }
}

При запуске нашего контекста он использует ServletContext для добавления слушателя ContextLoaderListener и регистрирует наш класс конфигурации как Servlet Filter. Обратите внимание, что это будет работать только в сервлет-контейнерах, совместимых с Servlet-3. Так что если вы используете Apache Tomcat, убедитесь, что его версия 7.0 или выше. Наш проект готов, просто разверните его в своем любимом сервлет-контейнере. Я использую Apache Tomcat-7 для запуска этого приложения. На следующих изображениях показан ответ в различных сценариях.

Доступ к HTML-странице без безопасности

Аутентификация не удалась из-за неверных учетных данных

Домашняя страница с аутентификацией Spring Security JDBC

Домашняя страница с аутентификацией DAO Spring Security UserDetailsService

Домашняя страница с аутентификацией в памяти Spring Security

Страница выхода

Если вы хотите использовать контейнер сервлетов, который не поддерживает спецификации сервлетов 3, то вам нужно зарегистрировать DispatcherServlet через дескриптор развёртывания. См. JavaDoc WebApplicationInitializer для получения более подробной информации. Это всё, что касается примера учебного пособия по Spring Security и его интеграции в веб-приложение на основе сервлетов. Пожалуйста, загрузите образец проекта по ссылке ниже и поиграйте с ним, чтобы узнать больше.

Загрузить проект Spring Servlet Security

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