Рубріки: Теория

Краткий обзор Spring Security, создание проекта и подключение зависимостей

Сергей Почекутов

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

В этой статье мы изучим основы Spring Security, а также попрактикуемся, создав простое приложение с регистрацией и аутентификацией пользователя.

Содержание:
1. Основные возможности
2. Пример подключения Spring Security
3. Создание проекта и подключение зависимостей
Итоги

1. Основные возможности

Шифрование паролей

Spring Security не ограничивается аутентификацией. Фреймворк также помогает решить проблему с безопасным хранением паролей.

Spring Security предлагает назначить объекту UserDetails собственный шифровальщик паролей. По умолчанию используется BCrypt. Также можно настроить количество раундов хеширования и реализацию случайного алгоритма.

Аутентификация In-Memory

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

Это полезно при разработке и тестировании. Реальная база данных при таком подходе остается нетронутой.

LDAP-аутентификация

Lightweight Directory Access Protocol (LDAP) — протокол аутентификации учетных записей пользователей в организациях. Позволяет определять структуру пользователей и групп пользователей, назначать им права доступа.

Дополнительную информацию вы можете получить в официальном руководстве по использованию аутентификации LDAP.

Управление сессией

Spring Security предоставляет механизмы для управления сеансом пользователя. Он создает эти механизмы контроля при входе в систему и уничтожает при выходе.

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

Remember Me Authentication

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

Spring Security предлагает несколько способов реализации этого типа аутентификации — например, хеширование данных с помощью секретного ключа или хранение постоянного токена в базе данных.

OAuth 2.0

Open Authorization 2.0 — открытый стандарт проверки прав пользователя с помощью сервиса авторизации. Он также используется для реализации таких функций, как вход через учетные записи Facebook, Google и других крупных площадок.

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

Это лишь некоторые возможности аутентификации и авторизации через Spring Security. Если хотите узнать больше, смотрите документацию Spring Boot.

Ознакомьтесь с курсами от наших партнеров, которые помогут вам повысить свой уровень в среде разработки приложений, от Mate Academy и Hillel. Практикующие преподаватели и единомышленники, это именно то что вам нужно для развития.

2. Пример подключения Spring Security

Посмотрим на практике, какие настройки включаются по умолчанию при добавлении в проект Spring Security.

Подготовка

Сгенерируйте на Spring Initializr приложение с зависимостью Web:

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

Напишите REST-контроллер:

@RestController
public class HelloController {
    @GetMapping("/api/hello")
    public String hello(){
        return "Highload";
    }
}

Проверьте приложение в браузере. Сейчас доступ к нему имеют все пользователи.

Подключение Spring Security

Добавьте Maven-зависимость, чтобы подключить Spring Security:

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

Проверьте приложение снова. Теперь при переходе по первоначальному адресу http://localhost:8080/api/hello мы попадем на страницу для ввода логина и пароля — http://localhost:8080/login. Здесь размещена форма входа. Введите в нее любые данные и нажмите Sign in — появится сообщение об ошибке.

Приложение уже выполняет проверку пользователей, хотя вы еще даже ничего не настраивали.

Проверка изменений

Spring Security после подключения добавляет своего пользователя. Его имя user, пароль генерируется при запуске. Кроме того, в проекте при добавлении зависимости:

  1. формируется страница, на которой размещена форма для ввода имени и пароля;
  2. сразу начинает работать проверка имени и пароля;
  3. все URL недоступны, пока вы не зайдете в систему под именем зарегистрированного пользователя.

Кроме страницы для входа создается страница для выхода из аккаунта. Она находится по адресу /logout.

Настройка аутентификации

Spring Security использует Form-Based-аутентификацию. Пользователь отправляет имя и пароль через форму. Сервер берет эти данные из запроса как POST-параметры.

Теперь посмотрим, как сервер хранит данные пользователей. Интересно, что они находятся не в базе, не на LDAP-сервере, а в оперативной памяти приложения. И хранятся там до тех пор, пока оно запущено. Такая аутентификация называется In-Memory authentication.

Чтобы отредактировать пользователя, необходимо заново запустить приложение. Для продакшена такой вариант не подходит, но для экспериментов в процессе разработки это простой и полезный метод.

Приложение хранит имя и пароль в памяти. Вы можете посмотреть эти данные через консоль. Чтобы каждый раз не лезть в консоль, воспользуйтесь файлом конфигурации application.yml. Зафиксируйте в нем имя и пароль пользователя.

spring:
  security:
    user:
      name: highload
      password: myhighload

Теперь пароль не будет каждый раз генерироваться заново. Аналогичным образом можно задать других пользователей. Это полезно для будущей проверки разграничения прав доступа.

Чтобы настроить In-Memory-аутентификацию, создайте класс SecurityConfig, который расширяет класс @EnableWebSecurity. Вот полный код:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder() {

        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("highload")
                   .password("myhighload")
                   .authorities("ROLE_USER")
                .and()
                .withUser("highload2")
                   .password("myhighload2")
                   .authorities("ROLE_USER");
    }
}

Теперь — пояснения. Здесь вы переопределяете метод configure AuthenticationManagerBuilder auth класса WebSecurityConfigurerAdapter, получаете доступ к билдеру AuthenticationManagerBuilde и уже через него настраиваете нужный AuthenticationManager.

В билдере нужно задать тип аутентификации. В этом примере используется In-Memory — auth.inMemoryAuthentication(). Но может быть другой вариант — Jdbc, LDAP, кастомная аутентификация. Тип аутентификации определяет, где хранится пользователь.

Затем определяются специальные настройки AuthenticationManager. Вы уточняете, как он извлекает пользователя из хранилища, чтобы сравнить его с введенными данными. В этом случае In-Memory-аутентификация позволяет ускорить процесс. Имя и пароль заданы в том же файле.

AuthenticationManager достает не только имя и пароль, но и разрешения пользователя — authorities("ROLE_USER").

Что в итоге делает приведенный выше фрагмент кода:

  1. Сравнивает переданные имя и пароль со значениями имени и пароля, которые хранятся в оперативной памяти.
  2. При совпадении данные аутентификация проходит успешно.

Обратите также внимание на бин PasswordEncoder. В нем указано, как шифровать пароль. В приведенном выше примере пароль не шифруется. Так можно делать только на учебных проектах для наглядности. В реальном приложении NoOpPasswordEncoder не используется.

3. Создание проекта и подключение зависимостей

Итак, мы готовы, чтобы написать обещанное простое приложение с регистрацией и входом в учетную запись пользователя. Используемые технологии: Spring Boot, Spring Security, Spring Data JPA, Hibernate, MySQL, JSP, Bootstrap и Docker Compose.

Создание проекта

Структура проекта должна выглядеть так:

├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── hellokoding
│       │           └── auth
│       │               ├── model
│       │               │   ├── Role.java
│       │               │   └── User.java
│       │               ├── repository
│       │               │   ├── RoleRepository.java
│       │               │   └── UserRepository.java
│       │               ├── service
│       │               │   ├── SecurityServiceImpl.java
│       │               │   ├── SecurityService.java
│       │               │   ├── UserDetailsServiceImpl.java
│       │               │   ├── UserServiceImpl.java
│       │               │   └── UserService.java
│       │               ├── validator
│       │               │   └── UserValidator.java
│       │               ├── web
│       │               │   └── UserController.java
│       │               ├── WebApplication.java
│       │               └── WebSecurityConfig.java
│       ├── resources
│       │   ├── application.properties
│       │   └── validation.properties
│       └── webapp
│           ├── resources
│           │   ├── css
│           │   │   ├── bootstrap.min.css
│           │   │   └── common.css
│           │   └── js
│           │       └── bootstrap.min.js
│           ├── login.jsp
│           ├── registration.jsp
│           └── welcome.jsp
├── Dockerfile
├── docker-compose.yml
└── pom.xml

Добавление зависимостей

Содержимое файла pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hellokoding.springboot</groupId>
    <artifactId>springboot-registration-login</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Определение объектов JPA и Hibernate

Содержимое файла User.java:

package com.hellokoding.auth.model;

import javax.persistence.*;
import java.util.Set;

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @Transient
    private String passwordConfirm;

    @ManyToMany
    private Set<Role> roles;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPasswordConfirm() {
        return passwordConfirm;
    }

    public void setPasswordConfirm(String passwordConfirm) {
        this.passwordConfirm = passwordConfirm;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

Содержимое файла Role.java:

package com.hellokoding.auth.model;

import javax.persistence.*;
import java.util.Set;

@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }
}

Использование репозиториев Spring Data JPA

Репозитории помогают сократить шаблонный код, который необходим для реализации уровней доступа к данным в разных хранилищах — MySQL, PostgreSQL и других. В них описываются некоторые функции для создания, чтения, обновления и удаления данных из БД — например, findAll, findById, save, saveAll, delete и deleteAll.

Файл UserRepository.java:

package com.hellokoding.auth.repository;

import com.hellokoding.auth.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

Файл RoleRepository.java:

package com.hellokoding.auth.repository;

import com.hellokoding.auth.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoleRepository extends JpaRepository<Role, Long>{
}

Определение UserDetailsService Spring Security

Для использования аутентификации с помощью Spring Security нужно реализовать интерфейс org.springframework.security.core.userdetails.UserDetailsService.

Файл UserDetailsServiceImpl.java:

package com.hellokoding.auth.service;

import com.hellokoding.auth.model.Role;
import com.hellokoding.auth.model.User;
import com.hellokoding.auth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.Set;

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsername(username);
        if (user == null) throw new UsernameNotFoundException(username);

        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
        for (Role role : user.getRoles()){
            grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
        }

        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
    }
}

Создание сервиса безопасности

Сервис безопасности нужен для предоставления текущего авторизованного пользователя и автоматического входа в систему после регистрации.

SecurityService.java:
package com.hellokoding.auth.service;

public interface SecurityService {
    String findLoggedInUsername();

    void autoLogin(String username, String password);
}
SecurityServiceImpl.java:
package com.hellokoding.auth.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

@Service
public class SecurityServiceImpl implements SecurityService{
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(SecurityServiceImpl.class);

    @Override
    public String findLoggedInUsername() {
        Object userDetails = SecurityContextHolder.getContext().getAuthentication().getDetails();
        if (userDetails instanceof UserDetails) {
            return ((UserDetails)userDetails).getUsername();
        }

        return null;
    }

    @Override
    public void autoLogin(String username, String password) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());

        authenticationManager.authenticate(usernamePasswordAuthenticationToken);

        if (usernamePasswordAuthenticationToken.isAuthenticated()) {
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            logger.debug(String.format("Auto login %s successfully!", username));
        }
    }
}

Создание службы пользователя

Также нужно создать службу для регистрации пользователей.

UserService.java:
package com.hellokoding.auth.service;

import com.hellokoding.auth.model.User;

public interface UserService {
    void save(User user);

    User findByUsername(String username);
}
UserServiceImpl.java:
package com.hellokoding.auth.service;

import com.hellokoding.auth.model.User;
import com.hellokoding.auth.repository.RoleRepository;
import com.hellokoding.auth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.HashSet;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public void save(User user) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        user.setRoles(new HashSet<>(roleRepository.findAll()));
        userRepository.save(user);
    }

    @Override
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }
}

Определение валидатора

Входные данные будет проверять валидатор. Коды ошибок определяются в validation.properties.

UserValidator.java:
package com.hellokoding.auth.validator;

import com.hellokoding.auth.model.User;
import com.hellokoding.auth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@Component
public class UserValidator implements Validator {
    @Autowired
    private UserService userService;

    @Override
    public boolean supports(Class<?> aClass) {
        return User.class.equals(aClass);
    }

    @Override
    public void validate(Object o, Errors errors) {
        User user = (User) o;

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "NotEmpty");
        if (user.getUsername().length() < 6 || user.getUsername().length() > 32) {
            errors.rejectValue("username", "Size.userForm.username");
        }
        if (userService.findByUsername(user.getUsername()) != null) {
            errors.rejectValue("username", "Duplicate.userForm.username");
        }

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty");
        if (user.getPassword().length() < 8 || user.getPassword().length() > 32) {
            errors.rejectValue("password", "Size.userForm.password");
        }

        if (!user.getPasswordConfirm().equals(user.getPassword())) {
            errors.rejectValue("passwordConfirm", "Diff.userForm.passwordConfirm");
        }
    }
}

Настройка контроллеров

UserController.java:
package com.hellokoding.auth.web;

import com.hellokoding.auth.model.User;
import com.hellokoding.auth.service.SecurityService;
import com.hellokoding.auth.service.UserService;
import com.hellokoding.auth.validator.UserValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private SecurityService securityService;

    @Autowired
    private UserValidator userValidator;

    @GetMapping("/registration")
    public String registration(Model model) {
        model.addAttribute("userForm", new User());

        return "registration";
    }

    @PostMapping("/registration")
    public String registration(@ModelAttribute("userForm") User userForm, BindingResult bindingResult) {
        userValidator.validate(userForm, bindingResult);

        if (bindingResult.hasErrors()) {
            return "registration";
        }

        userService.save(userForm);

        securityService.autoLogin(userForm.getUsername(), userForm.getPasswordConfirm());

        return "redirect:/welcome";
    }

    @GetMapping("/login")
    public String login(Model model, String error, String logout) {
        if (error != null)
            model.addAttribute("error", "Your username and password is invalid.");

        if (logout != null)
            model.addAttribute("message", "You have been logged out successfully.");

        return "login";
    }

    @GetMapping({"/", "/welcome"})
    public String welcome(Model model) {
        return "welcome";
    }
}

POST-контроллер /login определять не нужно. Его по умолчанию предоставляет Spring Security.

Шаблоны просмотра JSP (Bootstrap)

Здесь достаточно трех файлов: для регистрации, входа и приветствия.

Registration.jsp:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <title>Create an account</title>

      <link href="${contextPath}/resources/css/bootstrap.min.css" rel="stylesheet">
      <link href="${contextPath}/resources/css/common.css" rel="stylesheet">
  </head>

  <body>

    <div class="container">

        <form:form method="POST" modelAttribute="userForm" class="form-signin">
            <h2 class="form-signin-heading">Create your account</h2>
            <spring:bind path="username">
                <div class="form-group ${status.error ? 'has-error' : ''}">
                    <form:input type="text" path="username" class="form-control" placeholder="Username"
                                autofocus="true"></form:input>
                    <form:errors path="username"></form:errors>
                </div>
            </spring:bind>

            <spring:bind path="password">
                <div class="form-group ${status.error ? 'has-error' : ''}">
                    <form:input type="password" path="password" class="form-control" placeholder="Password"></form:input>
                    <form:errors path="password"></form:errors>
                </div>
            </spring:bind>

            <spring:bind path="passwordConfirm">
                <div class="form-group ${status.error ? 'has-error' : ''}">
                    <form:input type="password" path="passwordConfirm" class="form-control"
                                placeholder="Confirm your password"></form:input>
                    <form:errors path="passwordConfirm"></form:errors>
                </div>
            </spring:bind>

            <button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
        </form:form>

    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script src="${contextPath}/resources/js/bootstrap.min.js"></script>
  </body>
</html>
Login.jsp:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<c:set var="contextPath" value="${pageContext.request.contextPath}"/>

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="utf-8">
      <title>Log in with your account</title>

      <link href="${contextPath}/resources/css/bootstrap.min.css" rel="stylesheet">
      <link href="${contextPath}/resources/css/common.css" rel="stylesheet">
  </head>

  <body>

    <div class="container">
      <form method="POST" action="${contextPath}/login" class="form-signin">
        <h2 class="form-heading">Log in</h2>

        <div class="form-group ${error != null ? 'has-error' : ''}">
            <span>${message}</span>
            <input name="username" type="text" class="form-control" placeholder="Username"
                   autofocus="true"/>
            <input name="password" type="password" class="form-control" placeholder="Password"/>
            <span>${error}</span>
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

            <button class="btn btn-lg btn-primary btn-block" type="submit">Log In</button>
            <h4 class="text-center"><a href="${contextPath}/registration">Create an account</a></h4>
        </div>
      </form>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script src="${contextPath}/resources/js/bootstrap.min.js"></script>
  </body>
</html>
Welcome.jsp: 
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Create an account</title>
    <link href="${contextPath}/resources/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container">
    <c:if test="${pageContext.request.userPrincipal.name != null}">
        <form id="logoutForm" method="POST" action="${contextPath}/logout">
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
        </form>

        <h2>Welcome ${pageContext.request.userPrincipal.name} | <a >

Определение свойств

Validation.properties:
NotEmpty=This field is required.
Size.userForm.username=Please use between 6 and 32 characters.
Duplicate.userForm.username=Someone already has that username.
Size.userForm.password=Try one with at least 8 characters.
Diff.userForm.passwordConfirm=These passwords don't match.
Application.properties: 
spring.datasource.url=jdbc:mysql://hk-mysql:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=hellokoding
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

spring.mvc.view.prefix: /
spring.mvc.view.suffix: .jsp
spring.messages.basename=validation

Конфигурация веб-безопасности

WebSecurityConfig.java:
package com.hellokoding.auth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Qualifier("userDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**", "/registration").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    public AuthenticationManager customAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

Конфигурация приложения

WebApplication.java:
package com.hellokoding.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class WebApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(WebApplication.class, args);
    }
}

Запуск Docker-контейнера

Подготовьте Dockerfile для приложения Java / Spring Boot и файл docker-compose.yaml для MySQL Server.

Содержимое Dockerfile:

FROM maven:3.5-jdk-8

Содержимое docker-compose.yml:

version: '3'
services:
  hk-mysql:
    container_name: hk-mysql
    image: mysql/mysql-server:5.7
    environment:
      MYSQL_DATABASE: test
      MYSQL_ROOT_PASSWORD: hellokoding
      MYSQL_ROOT_HOST: '%'
    ports:
    - "3306:3306"
    restart: always

  registration-login:
    build: .
    volumes:
    - .:/app
    - ~/.m2:/root/.m2
    working_dir: /app
    ports:
    - 8080:8080
    command: mvn clean spring-boot:run
    depends_on:
    - hk-mysql

Перейдите в корень проекта и запустите Docker:

docker-compose up

Тестирование

Чтобы запустить приложение на локальном сервере MySQL, измените в application.properties «hk-mysql» на «localhost», перейдите в корневой каталог проекта и выполните команду:

mvn clean spring-boot:run

Перейдите в браузере по адресу localhost: 8080 и протестируйте приложение. Попробуйте заходить под разными пользователями, выходить, добавлять новые функции и изменять права доступа.

Исходный код нашего проекта — в репозитории на GitHub.

Итоги

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

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023