Поиск по сайту:

Учебное пособие по Spring Security


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

Весенняя безопасность

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

Пример безопасности Spring

  1. в памяти
  2. ДАО
  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.

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

Вот наш последний файл 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>

У нас есть следующие зависимости, связанные с Spring Framework.

  1. spring-jdbc: используется для операций JDBC методом аутентификации JDBC. Требуется установка DataSource как JNDI. Полный пример его использования см. в примере Spring DataSource JNDI
  2. spring-security-taglibs: библиотека тегов безопасности Spring, я использовал ее для отображения ролей пользователей на странице JSP. Однако в большинстве случаев он вам не понадобится.
  3. spring-security-config: используется для настройки поставщиков аутентификации, использования JDBC, DAO, LDAP и т. д.
  4. spring-security-web: этот компонент интегрирует Spring Security в API сервлетов. Он нужен нам для добавления нашей конфигурации безопасности в веб-приложение.

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

Страницы просмотра примеров Spring Security

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

Я включил index.jsp как welcome-file в дескриптор развертывания приложения. Spring Security позаботится об атаке CSRF, поэтому, когда мы отправляем форму для выхода из системы, мы отправляем токен CSRF обратно на сервер, чтобы удалить его. Объект CSRF, установленный компонентом Spring Security, — это _csrf, и мы используем его имя свойства и значение токена для передачи в запросе на выход из системы. Давайте теперь посмотрим на конфигурации Spring Security.

Пример реализации 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");
		}
		
		//creating dummy user details, should do JDBC operations
		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<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
				auths.add(new SimpleGrantedAuthority("admin"));
				return auths;
			}
		};
	}

}

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

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

Мы можем реализовать интерфейс 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 {

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

		// using custom UserDetailsService DAO
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// using 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 should completely ignore URLs ending with .html
                .antMatchers("/*.html");
    }

}

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

Интеграция 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 для добавления фильтра сервлетов. Обратите внимание, что это будет работать только в контейнерах сервлетов жалоб Servlet-3. Поэтому, если вы используете Apache Tomcat, убедитесь, что его версия 7.0 или выше. Наш проект готов, просто разверните его в своем любимом контейнере сервлетов. Я использую Apache Tomcat-7 для запуска этого приложения. На изображениях ниже показан ответ в различных сценариях.

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

Ошибка аутентификации из-за неверных учетных данных

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

Домашняя страница с Spring Security UserDetailsService DAO Authentication

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

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

Скачать проект Spring Servlet Security