Учебные материалы

Безопасность Web-приложения

Этот урок освещает процесс создания простого web-приложения с ресурсами, которые защищены Spring Security.

Что вы создадите

Вы создадите Spring MVC приложение, которое обеспечивает защиту страницы входа фиксированным списком пользователей.

Что вам потребуется

  • Примерно 15 минут свободного времени
  • Любимый текстовый редактор или IDE
  • JDK 6 и выше
  • Gradle 1.11+ или Maven 3.0+
  • Вы также можете импортировать код этого урока, а также просматривать web-страницы прямо из Spring Tool Suite (STS), собственно как и работать дальше из него.

Как проходить этот урок

Как и большинство уроков по Spring, вы можете начать с нуля и выполнять каждый шаг, либо пропустить базовые шаги, которые вам уже знакомы. В любом случае, вы в конечном итоге получите рабочий код.

Чтобы начать с нуля, перейдите в Настройка проекта.

Чтобы пропустить базовые шаги, выполните следующее:

Когда вы закончите, можете сравнить получившийся результат с образцом в gs-securing-web/complete.

Настройка проекта

Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle и Maven. Если вы не знакомы ни с одним из них, ознакомьтесь с соответсвующими уроками Сборка Java-проекта с использованием Gradle или Сборка Java-проекта с использованием Maven.

Создание структуры каталогов

В выбранном вами каталоге проекта создайте следующую структуру каталогов; к примеру, командой mkdir -p src/main/java/hello для *nix систем:

└── src
    └── main
        └── java
            └── hello

Создание файла сборки Gradle

Ниже представлен начальный файл сборки Gradle. Файл pom.xml находится здесь. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.

Если вы посмотрите на pom.xml, вы найдете, что указана версия для maven-compiler-plugin. В общем, это не рекомендуется делать. В данном случае он предназначен для решения проблем с нашей CI системы, которая по умолчанию имеет старую(до Java 5) версию этого плагина.

build.gradle

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.8.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'gs-securing-web'
    version =  '0.1.0'
}

repositories {
    mavenLocal()
    mavenCentral()
    maven { url "http://repo.spring.io/libs-release" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

Spring Boot gradle plugin предоставляет множество удобных возможностей:

  • Он собирает все jar'ы в classpath и собирает единое, исполняемое "über-jar", что делает более удобным выполнение и доставку вашего сервиса
  • Он ищет public static void main() метод, как признак исполняемого класса
  • Он предоставляет встроенное разрешение зависимостей, с определенными номерами версий для соответсвующих Spring Boot зависимостей. Вы можете переопределить на любые версии, какие захотите, но он будет по умолчанию для Boot выбранным набором версий

Создание незащищенного web приложения

До того, как применить защиту к вашему приложению, вам необходимо само приложение. Шаги в этой главе освещают процесс создания очень простого web-приложения. Затем в следующей главе вы защитите его с помощью Spring Security.

Приложение включает два простых представления: домашнюю страницу и "Hello World" страницу. Домашняя страница предствлена ниже как Thymeleaf шаблон:

src/main/resources/templates/home.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example</title>
    </head>
    <body>
        <h1>Welcome!</h1>

        <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
    </body>
</html>

Как вы можете увидеть, это простое приложение включает ссылку на страницу "/hello", которая представлена ниже также как Thymeleaf шаблон.

src/main/resources/templates/hello.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1>Hello world!</h1>
    </body>
</html>

Приложение основано на Spring MVC. Т.о. вам необходимо настроить Spring MVC и контроллеры представлений для отображения этих шаблонов. Ниже конфигурационный класс для настройки Spring MVC в приложении.

src/main/java/hello/MvcConfig.java

package hello;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }

}

Метод addViewControllers() (переопределение метода с таким же названием в WebMvcConfigurerAdapter), добавляющий четыре контроллера. Двое из них настроены на представление с именем "home"(home.html), другой настроен на "hello". Четвертый контроллер настроен на представление с названием "login". Вы создадите это представление в следующей главе.

На данном этапе вы можете перейти к созданию приложения исполняемым и запустить приложени без входа в систему.

Фундамент простого web-приложения создано, теперь вы можете добавить защиту к нему.

Настройка Spring Security

Предположим, что вы хотите предотвратить неавторизованный доступ к просмотру представления "/hello". Сейчас, если пользователь нажмет на ссылку на домашней странице, он увидит приветствие без каких либо помех к нему. Вам необходимо добавить барьер, который заставляет пользователя войти в систему до того, как он увидит страницу.

Для этого вы настраиваете Spring Security в приложении. Если Spring Security в classpath'е, Spring Boot автоматически обеспечивает защиту для всех HTTP путей с "базовой" аутентификацией. Но вы сможете в дальнейшем изменить под себя настройки безопасности. Первое, что вам нужно сделать, это добавить Spring Security в classpath.

C Gradle это достигается одной строчкой в блоке dependencies:

build.gradle

dependencies {
    ...
    compile("org.springframework.boot:spring-boot-starter-security")
    ...
}

C Maven - добавлением дополнительного блока в <dependencies>:

pom.xml

<dependencies>
    ...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    ...
</dependencies>

Ниже приведена настройка безопасности, которая гарантирует, что только авторизованные пользователи могут увидеть секретное приветствие:

src/main/java/hello/WebSecurityConfig.java

package hello;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated();
        http
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Configuration
    protected static class AuthenticationConfiguration extends
            GlobalAuthenticationConfigurerAdapter {

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER");
        }

    }

}

Класс WebSecurityConfig содержит аннотацию @EnableWebMvcSecurity для включения поддержки безопасности Spring Security и Spring MVC интеграцию. Он также расширяет WebSecurityConfigurerAdapter и переопределяет пару методов для установки некоторых настроек безопасности.

Метод configure(HttpSecurity) определяет, какие URL пути должны быть защищены, а какие нет. В частности, "/" и "/home" настроены без требования к авторизации. Ко всем остальным путям должна быть произведена аутентификация.

Когда пользователь успешно войдет в систему, он будет перенаправлен на предыдущую запрашиваемую страницу, требующую авторизацию. Здесь вы определили собственную "/login" страницу в loginPage() и каждый имеет доступ к ней.

Что касается метода configure(AuthenticationManagerBuilder), то он создает в памяти хранилище пользователей с единственным пользователем. Этому пользователю дано имя "user", пароль "password" и роль "ROLE".

Теперь нам нужно создать страницу входа. Т.к. уже есть сопоставление контроллера с представлением "login", то вам нужно только создать это представления входа:

src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

Как видите, этот Thymeleaf шаблон просто представляет собой форму, которая собирает значения имени пользователя и пароля и отправляет их к "/login". Рассмотрим, как настроен Spring Security фильтр, который перхватывает запрос и идентифицирует пользователя. Если пользователь не прошел проверку, то он будет перенаправлен на страницу "/login?error" и наша страница отобразит соответсвующее сообщение об ошибке. При удачной авторизации, приложение отправит к "/login?logout" и наша страница отобразит соответственное сообщение об успехе.

Последнее, что нам нужно, это предоставить пользователю способ для отображения текущего имени и выхода из системы. Обновим hello.html, чтобы сказать привет текущему пользователю и добавить в форму "Sign Out", как показано ниже:

src/main/resources/templates/hello.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Sign Out"/>
        </form>
    </body>
</html>

Мы отображаем имя пользователя с использованием интеграции Spring Security с HttpServletRequest#getRemoteUser(). По кнопке "Sign Out" форма отправляет POST запрос к "/logout". При успешном выходе пользователь будет перенаправлен к "/login?logout".

Создание приложения исполняемым

Несмотря на то, что пакет этого сервиса может быть в составе web-приложения и WAR файлов, более простой подход, продемонстрированный ниже создает отдельное самостоятельное приложение. Вы упаковываете все в единый, исполняемый JAR-файл, который запускается через хорошо знакомый старый main() Java-метод. Попутно, вы используете поддержку Spring для встроенного Tomcat контейнера сервлетов как HTTP среду выполнения вместо развертывания на сторонний экземпляр.

src/main/java/hello/Application.java

package hello;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {

    public static void main(String[] args) throws Throwable {
        SpringApplication.run(Application.class, args);
    }

}

main() метод передает управление вспомогательному классу SpringApplication, где Application.class - аргумент его run() метода. Это сообщает Spring о чтении метаданных аннотации из Application и управлении ею как компонента в Spring application context.

Аннотация @ComponentScan говорит Spring'у рекурсивно искать в пакете hello и его потомках классы, помеченные прямо или косвенно Spring аннотацией @Component. Эта директива гарантирует, что Spring найдет и зарегистрирует WebConfig и WebSecurityConfig, потому что он отмечен @Configuration, который, в свою очередь, является своего рода @Component аннотацией. По факту, эти конфигурационные классы также используются для настройки Spring.

Аннотация @EnableAutoConfiguration переключает на приемлемое по умолчанию поведение, основанное на содержании вашего classpath. К примеру, т.к. приложение зависит от встраиваемой версии Tomcat (tomcat-embed-core.jar), Tomcat сервер установлен и сконфигурирован на приемлемое по умолчанию поведение от вашего имени. И т.к. приложение также зависит от Spring MVC (spring-webmvc.jar), Spring MVC DispatcherServlet сконфигурирован и зарегестрирован для вас - web.xml не нужен! Автоконфигурация полезный и гибкий механизм. Подробную информацию смотрите в API документации.

Сборка исполняемого JAR

Вы можете собрать единый исполняемый JAR-файл, который содержит все необходимые зависимости, классы и ресурсы. Это делает его легким в загрузке, версионировании и развертывании сервиса как приложения на протяжении всего периода разработки, на различных средах и так далее.

./gradlew build

Затем вы можете запустить JAR-файл:

java -jar build/libs/gs-securing-web-0.1.0.jar

Если вы используете Maven, вы можете запустить приложение, используя mvn spring-boot:run, либо вы можете собрать приложение с mvn clean package и запустить JAR примерно так:

java -jar target/gs-securing-web-0.1.0.jar
Процедура, описанная выше, создает исполняемый JAR. Вы также можете вместо него собрать классический WAR-файл.

Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:

./gradlew clean build && java -jar build/libs/gs-securing-web-0.1.0.jar
Если вы используете Maven, то можете запустить ваш сервис таким образом: mvn clean package && java -jar target/gs-securing-web-0.1.0.jar.

Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:

./gradlew bootRun
С mvn - mvn spring-boot:run.
... app starts up ...

как только приложение запустится, откройте в вашем браузере адрес http://localhost:8080. Вы должны увидеть домашнюю страницу:

Когда вы нажмете на ссылку, то она попытается открыть вам сстраницу приветствия /hello. Но т.к. эта страница защищена и вы ещё не вошли в систему, вы увидите страницу входа:

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

На странице входа войдите под тестовым пользователем, введя "user" и "password" в соответствующие поля. Как только вы отправите форму, вы авторизуетесь и увидите страницу приветствия:

Если вы нажмете на кнопку "Sign Out", ваша авторизация отменится и вы вернетесь к странице входа с сообщением о том, что вы вышли из системы.

Итог

Поздравляем! Вы только что разработали простое web-приложение, которое защищено Spring Security.

С оригинальным текстом урока вы можете ознакомиться на spring.io.

comments powered by Disqus