Это руководство поможет вам защитить приложение Spring MVC с помощью Spring Security. Вы узнаете, как:

  1. Защитите приложение весенней загрузки с помощью электронной почты и пароля.
  2. Загрузить пользователя из базы данных.

Создание приложения Spring MVC

Создайте приложение Spring MVC из Spring Initializr с Java версии 17. Создайте новый пакет с именем controllers. Затем создайте новый контроллер с именем HomeController.java внутри только что созданного пакета контроллеров. Напишите следующий код внутри HomeController.java:

package com.example.demo.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("")
public class HomeController {

    @ResponseBody
    @RequestMapping(value = "/",method = RequestMethod.GET)
    public String index(){
        return "hello world";
    }

    @ResponseBody
    @RequestMapping(value = "/about",method = RequestMethod.GET)
    public String about(){
        return "about";
    }
}

Мы создали два URL-адреса: "/" и "/about". Теперь запустите приложение через IDE или через командную строку с помощью следующей команды:

mvn spring-boot:run 

Если вы посетите http://localhost:8080/, то вы получите следующую страницу.

Или вы можете посетить http://localhost:8080/about и получить аналогичную страницу.

Защита маршрутов

Теперь мы хотим защитить наши URL-маршруты с помощью имени пользователя и пароля. Добавьте следующие зависимости в файл pom.xml:

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

  <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-test</artifactId>
   <scope>test</scope>
  </dependency>

Теперь повторно запустите приложение. Если вы хотите посетить предыдущие два URL-адреса, вы сначала будете перенаправлены на страницу входа. Используйте имя пользователя: user. Spring boot создаст пароль для входа в систему, вы можете найти его в терминале:

Чтобы выйти из системы, перейдите на http://localhost:8080/logout. Каждый раз, когда вы запускаете приложение, Spring Boot будет генерировать для вас другой пароль. Мы не хотим использовать имя пользователя и пароль, предоставленные Spring. Мы предоставим собственное имя пользователя и пароль. Чтобы настроить безопасность spring, создайте новый пакет с именем config, создайте класс Java с именем SecurityConfig.java и добавьте следующий код:

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests()
                .anyRequest().authenticated();
        http
                .formLogin();

        return http.build();
    }

    @SuppressWarnings("deprecation")
    @Bean
    public NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

}

Мы использовали две аннотации: «@Configuration» и «@EnableWebSecurity». Эти аннотации сообщают безопасности Spring использовать нашу пользовательскую конфигурацию безопасности вместо конфигурации по умолчанию. Мы создали компонент SecurityFilterChain, который реализует нашу пользовательскую логику фильтрации.

http
     .authorizeHttpRequests()
     .anyRequest().authenticated();

Эти строки говорят весенней загрузке аутентифицировать каждый HTTP-запрос.

http.formLogin();

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

Мы также создали еще один компонент NoOpPasswordEncoder. Мы используем этот bean-компонент, потому что нам не нужна какая-либо кодировка (например, Bcrypt) для наших паролей. Это означает, что мы храним обычный текст в базе данных в качестве паролей. На практике мы не храним простые текстовые пароли в базе данных. Но если мы запустим приложение, нам все равно придется использовать имя пользователя и пароль по умолчанию, предоставленные Spring Security. Прежде чем предоставить пользовательское имя пользователя и пароль, нам нужно создать класс модели User. Создайте пакет с именем model и создайте класс с именем User :

package com.example.demo.model;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private long id;

    private String email;

    private String password;

    private String firstName;

    private String lastName;

    private String profilePhotoUrl;

    private String country;
 
    private String occupation;

    private Date dateOfBirth;

    private String gender;

    private String role;

    public User(String email, String password, String firstName, String lastName) {
        this.email = email;
        this.password = password;
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Мы использовали несколько аннотаций с классом User. «@Data», «@NoArgsConstructor» и «@AllArgsConstructor». Эти аннотации создают методы получения, установки и конструкторы. Чтобы использовать эти аннотации, добавьте следующую зависимость в файл pom.xml:

  <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.24</version>
   <scope>provided</scope>
  </dependency>

UserDetailsService и UserRepository

Безопасность Spring использует интерфейс с именем UserDetailsService для загрузки сведений о пользователе и сопоставления пользователя с пользовательским вводом. По умолчанию Spring будет соответствовать имени пользователя и паролю по умолчанию, предоставленным Spring Security. Чтобы предоставить наши собственные имена пользователей и пароли, мы должны реализовать нашу собственную службу пользовательских сведений о пользователе. Нам также необходимо создать репозиторий для загрузки сведений о пользователе из базы данных. Сначала создайте репозиторий с именем UserRepository.java в пакете «repositories»:

package com.example.demo.repositories;


import com.example.demo.model.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public User findUserByEmail(String email){
        return new User(email,"123456","Farhan","Tanvir");
    }
}

Мы создали метод внутри класса UserRepository для поиска пользователей по адресам электронной почты. Этот метод будет искать пользователя по электронной почте из базы данных и возвращать его. В нашем примере мы возвращаем статический объект User с паролем: «123456». Мы используем простой текстовый пароль, потому что мы не использовали кодировку для паролей. UserDetailsService будет использовать этот пароль для проверки пользователя. Давайте создадим наш собственный UserDetailsService. Создайте новый классCustomUserDetailsService внутри нового пакета «services»:

package com.example.demo.services;

import com.example.demo.model.User;
import com.example.demo.repositories.UserRepository;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findUserByEmail(email);
        List<String> roles = Arrays.asList(user.getRole());
        UserDetails userDetails =
                org.springframework.security.core.userdetails.User.builder()
                        .username(user.getEmail())
                        .password(user.getPassword())
                        .roles("USER")
                        .build();

        return userDetails;
    }
}

Мы внедряем объект UserRepository, используя внедрение зависимостей, и переопределяем метод loadUserByUsername(), который возвращает объект UserDetails. В нашей реализации loadUserByUsername() получает пользователя по адресу электронной почты из UserRepository , создает из него объект UserDetails и затем возвращается. Безопасность Spring будет внутренне вызывать этот метод с предоставленным пользователем адресом электронной почты, а затем сопоставлять пароль из объекта UserDetails с паролем, предоставленным пользователем.

Теперь сохраните и запустите приложение. Поскольку мы всегда возвращаем статического пользователя с паролем: «123456», не имеет значения, какой адрес электронной почты/имя пользователя мы вводим. Используйте пароль «123456» для входа, иначе вы получите ошибку аутентификации.

Весь процесс показан на изображении выше.

Загрузка сведений о пользователе из базы данных

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

Добавьте следующие зависимости в файл pom.xml:

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
  
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  
  <dependency>
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
   <scope>runtime</scope>
  </dependency>

Мы добавили 3 зависимости для: JDBC, JPA и PostgreSQL. JDBC — это стандарт низкого уровня для работы с базой данных. JDBC выполняет необработанные SQL-запросы. С другой стороны, JPA — это ORM (реляционное сопоставление объектов), которое сопоставляет классы Java с таблицей базы данных. JPA — это высокоуровневый API для взаимодействия с базой данных. В этой статье мы будем использовать JPA для SQL-запросов.

Сначала создайте базу данных postgresql локально или в облаке и создайте таблицу «users». Запустите следующий SQL-запрос, чтобы создать таблицу с именем «users»:

CREATE TABLE IF NOT EXISTS users (
    id serial PRIMARY KEY,
    country VARCHAR(255),
    date_of_birth TIMESTAMP NOT NULL,
    email VARCHAR(255),
    first_name VARCHAR(255),
    gender VARCHAR(255),
    last_name VARCHAR(255),
    occupation VARCHAR(255),
    password VARCHAR(255),
    profile_photo_url VARCHAR(255),
    role VARCHAR(255)
);

Давайте вставим пользователя в нашу базу данных, выполнив следующий запрос:

INSERT INTO users(first_name, last_name, email, password, profile_photo_url, country, occupation, gender, role, date_of_birth)
VALUES ('James','Smith','[email protected]','james123','','Australia','Doctor','Male','USER',timestamp '1987-01-10');

Мы добавили пользователя по имени «Джеймс Смит» в нашу базу данных. Давайте вставим еще одного пользователя:

INSERT INTO users(first_name, last_name, email, password, profile_photo_url, country, occupation, gender, role, date_of_birth)
VALUES ('Yousuf','Hussain','[email protected]','yousuf22','','Egypt','Teacher','Male','USER',timestamp '1984-01-11');

Настройка базы данных в Spring Boot

В каталоге ресурсов src › main › есть файл с именем «application.properties». Запишите в этот файл следующие свойства:

# PostgreSQL connection settings
spring.datasource.url=jdbc:postgresql://localhost:5432/database_name
spring.datasource.username=your_database_username
spring.datasource.password=your_database_password
spring.datasource.driver-class-name=org.postgresql.Driver

# HikariCP settings
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=20
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.poolName=HikariPoolBooks

# JPA settings
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.jdbc.batch_size=15
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.show-sql=true
spring.oracle.persistence.unit=postgresql

Здесь я создал базу данных postgresql на локальном хосте и использовал ее параметры подключения. Используйте собственный пароль базы данных и параметры подключения.

Использование JPA для запросов к базе данных

JPA сопоставляет классы Java с таблицами базы данных. Мы будем использовать наш класс User для таблицы users в базе данных. Обновите наш класс User следующим образом:

package com.example.demo.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "USERS")
public class User {

    @Id
    @Column(name = "ID")
    private long id;
    @Column(name = "EMAIL")
    private String email;
    @Column(name = "PASSWORD")
    private String password;
    @Column(name = "FIRST_NAME")
    private String firstName;
    @Column(name = "LAST_NAME")
    private String lastName;
    @Column(name = "PROFILE_PHOTO_URL")
    private String profilePhotoUrl;
    @Column(name = "COUNTRY")
    private String country;
    @Column(name = "OCCUPATION")
    private String occupation;
    @Column(name = "DATE_OF_BIRTH")
    private Date dateOfBirth;
    @Column(name = "GENDER")
    private String gender;
    @Column(name = "ROLE")
    private String role;

    public User(String email, String password, String firstName, String lastName) {
        this.email = email;
        this.password = password;
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Мы добавили некоторые аннотации с нашим классом и свойствами. Аннотация "@Entity" указывает jpa использовать этот класс в качестве таблицы. Аннотации "@Table" сообщают jpa, с какой таблицей сопоставить этот класс. Аннотация "@Column" над свойствами указывает соответствующие имена столбцов для свойств класса. Теперь давайте обновим наш класс UserRepository. Вместо того, чтобы возвращать статического пользователя, теперь он будет извлекать пользователя из базы данных. Обновите UserRepository.java следующим образом:

package com.example.demo.repositories;

import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User,Long> {

    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findUserByEmail(@Param("email") String email);
}

Мы объявили UserRepository как интерфейс, расширяющий интерфейс JpaRepository. Мы обновили функцию findUserByEmail(), добавив аннотацию «@Query». В этой аннотации мы указываем sql-запрос. Мы внедряем переменную метода «email» в sql с помощью «:email».

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

имя пользователя: [email protected]

пароль: Джеймс123

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

UserRepository не нашел ни одного пользователя с таким адресом электронной почты. Итак, он вернул нулевого пользователя. Чтобы правильно обработать эту ошибку, обновим метод loadUserByUsername() в классе CustomUserDetailsService:

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    User user = userRepository.findUserByEmail(email);
    if(user == null){
        throw new UsernameNotFoundException("No user found with email");
    }
    List<String> roles = Arrays.asList(user.getRole());
    UserDetails userDetails =
            org.springframework.security.core.userdetails.User.builder()
                    .username(user.getEmail())
                    .password(user.getPassword())
                    .roles("USER")
                    .build();

    return userDetails;
}

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

Хотите профессиональное веб-приложение для своего бизнеса или стартапа? Свяжитесь со мной