Registration and Login using Spring Boot, Spring Security, Spring Data JPA, Hibernate, H2, JSP and Bootstrap

Начальный проект

Spring Initializr использовался со следующими настройками:

Отсюда я создал базовый REST-контроллер для выполнения регистрации, авторизации и получения пользовательских данных. Вы можете увидеть начало этого сервиса в коммите spring-boot-webflux-jwt-authentication-example#4d149ded77bec3da9ad269e2feb30e81d10d774e.

Базовый пользовательский контроллер:

Что насчёт тестирования?

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

Что дальше?

Здесь мы прошли по основам JWT-аутентификации, но при этом очень многое в этом решении было упущено. Например:

  1. Как разрешить обновление JWT-токена до момента истечения его срока действия?
  2. Как другие сервисы аутентифицируют JWT?
  3. Если токен генерируется при запуске, то как это будет работать для мультиузловой системы?

Я надеюсь, что в последующих своих статьях смогу осветить перечисленные моменты на конкретных примерах.

Если же вам интересно итоговое состояние текущего приложения, взгляните на этот коммит.

Технологии

Для решения используем фреймворк Spring Boot и Spring Web, для него требуется:

  1. Java 8 ;

  2. Apache Maven

Авторизация и валидация будет выполнена силами Spring Security и JsonWebToken (JWT).Для уменьшения кода использую Lombok.

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

Создаем Maven-проект SpringBootSecurityRest. При инициализации, если вы это делаете через Intellij IDEA, добавьте Spring Boot DevTools, Lombok и Spring Web, иначе добавьте зависимости отдельно в pom-файле.

2 Конфигурация pom-xml

После развертывания проекта pom-файл должен выглядеть следующим образом:

  1. Должен быть указан parent-сегмент с подключенным spring-boot-starter-parent;

  2. И установлены зависимости spring-boot-starter-web, spring-boot-devtools и Lombok.

3 Создание ресурса REST

Разделим все классы на слои, создадим в папке com.springbootsecurityrest четыре новые папки:

1 Подключаем зависимости

Добавляем новые зависимости в pom-файл.

2 Генерация и хранения токена

Начнем с генерации и хранения токена, для этого создадим папку security и в ней создаем класс JwtTokenRepository с имплементацией интерфейса CsrfTokenRepository (из пакета org.springframework.security.web.csrf).

Интерфейс указывает на необходимость реализовать три метода:

  1. Генерация токена в методе generateToken;

  2. Сохранения токена – saveToken;

  3. Получение токена – loadToken.

Генерируем токен силами Jwt, пример реализации метода.

3 Создание нового фильтра для SpringSecurity

Создаем новый класс JwtCsrfFilter, который является реализацией абстрактного класса OncePerRequestFilter (пакет org.springframework.web.filter). Класс будет выполнять валидацию токена и инициировать создание нового. Если обрабатываемый запрос относится к авторизации (путь /auth/login), то логика не выполняется и запрос отправляется далее для выполнения базовой авторизации.

6 Обработка ошибок

Что бы видеть ошибки авторизации или валидации токена, необходимо подготовить обработчик ошибок. Для этого создаем новый класс GlobalExceptionHandler в корне com.springbootsecurityrest, который является расширением класса ResponseEntityExceptionHandler с реализацией метода handleAuthenticationException.

Метод будет устанавливать статус ответа 401 (UNAUTHORIZED) и возвращать сообщение в формате ErrorInfo.

7 Настройка конфигурационного файла Spring Security.

Все данные подготовили и теперь необходимо настроить конфигурационный файл. В папке com.springbootsecurityrest создаем файл SpringSecurityConfig, который является реализацией абстрактного класса WebSecurityConfigurerAdapter пакета org.springframework.security.config.annotation.web.configuration. Помечаем класс двумя аннотациями: Configuration и EnableWebSecurity.

Spring Validator

To provide input-data validation for /registration controller with Spring Validator, we implement

org.springframework.validation.Validator:

Controller Layer

The controller layer acts as an interface between View and Model. It receives requests from the View layer and processes them, including the necessary validations.

View Layer

This layer represents the output of the application, usually some form of UI.

The presentation layer is used to display the Model data fetched by the ControllerWe are using JSP as the view layer in this example.

Jwt token

JWT-токен — это JSON Web Token. Он используется для представления защищенной идентификационной информации (claims) между двумя сторонами. Дополнительную информацию о JWT-токенах вы можете найти на сайте

Мы собираемся сделать OAuth2-приложение, использующее JWT-токены, которое будет включать в себя сервер авторизации и сервер ресурсов.

Для начала нам нужно добавить зависимости в наш файл сборки.

Пользователи maven могут добавить следующие зависимости в pom.xml.

Примечание переводчика — для java старше 9 надо также добавить следующие зависимости:

Пользователи Gradle могут добавить следующие зависимости в файл

build.gradle

compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')

compile("org.springframework.security.oauth:spring-security-oauth2")
compile('org.springframework.security:spring-security-jwt')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("com.h2database:h2:1.4.191") 

где,


Полный файл

pom.xml

приведен ниже.

Jwt-аутентификация в spring boot webflux

Spring Security всегда снижал мой интерес к собственным проектам. Как только возникала необходимость выяснить как аутентифицировать пользователей, я сразу начинал испытывать негодование или скуку и просто садился играть в игры. Работая над личным проектом, я хотел добавить в пользовательскую службу JWT аутентификацию, чтобы другие службы могли независимо аутентифицировать запрос.

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

Oauth2

OAuth2 — это протокол авторизации, который позволяет клиенту (третьей стороне) получить доступ к ресурсам вашего приложения. Для построения OAuth2-приложения нам необходимо знать Grant Type (код авторизации), Client ID и Client secret.

Registration and login with spring boot spring security thymeleaf

package com.knf.dev.model;

import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.JoinColumn;

@Entity
@Table(name = “user”, uniqueConstraints =
@UniqueConstraint(columnNames = “email”))
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = “first_name”)
private String firstName;

@Column(name = “last_name”)
private String lastName;

private String email;

private String password;

@ManyToMany(fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
@JoinTable(name = “users_roles”,
joinColumns = @JoinColumn(name = “user_id”,
referencedColumnName = “id”),
inverseJoinColumns = @JoinColumn
(name = “role_id”,
referencedColumnName = “id”))
private Collection<Role> roles;

public User() {

}

public User(String firstName, String lastName,
String email, String password,
Collection<Role> roles) {

this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
this.roles = roles;
}

public Long getId() {
return id;
}

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

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getPassword() {
return password;
}

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

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

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

Validation.properties file

Create validation.properties file under resource folder and add the following content to it:

Подключение к spring security

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

Эти фильтры имеют чёткий порядок, определяемый SecurityWebFilterOrder. Например, процесс аутентификации (подтверждающий, что вы являетесь действительным пользователем системы) должен быть выполнен перед авторизацией (подтверждением наличия у вас доступа к этому ресурсу.)

Получение пользователя в контроллере

Заключительным шагом будет получение объекта Authentication, чтобы иметь возможность использовать текущего авторизованного пользователя внутри контроллера. Так как Webflux не позволяет использовать ThreadLocals, он хранится в контексте Mono, а не как объект SecurityContext. ReactiveSecurityContextHolder может использоваться для лёгкого получения контекста, но лучше, если он будет автоматически внедряться в сигнатуру функции конечной точки REST.

Создайте приложение для входа с spring boot, spring security, jpa

Статья основана на:

  • Spring Boot 2.x
  • Spring Security
  • JPA
  • Thymeleaf (Or JSP)
  • Database: MySQL, SQL Server, Oracle, Postgres

В данной статье я покажу вам как создать приложение Login, используя Spring Boot Spring Security JPA Thymeleaf. И объясню принцип работы Spring Security.

Пользователь обязан войти в систему, чтобы просмотреть защищенные страницы:

Пользователи, которые вошли в систему могут просматривать страницы только в рамках их роли. Если они будут просматривать страницы вне их роли, доступ будет отказан.

Смотрите так же:

В базе данных у нас есть 3 таблицы APP_USER, APP_ROLE, USER_ROLE это таблицы, которые должны вас заинтересовать. Помимо этого, другая таблица это PERSISTENT_LOGINS, данная таблица используется Spring Remember Me API для хранения информацииToken, и времени последнего входа в систему каждого пользователя.

MySQL


-- Create table
create table APP_USER
(
  USER_ID           BIGINT not null,
  USER_NAME         VARCHAR(36) not null,
  ENCRYTED_PASSWORD VARCHAR(128) not null,
  ENABLED           BIT not null  
) ;
--  
alter table APP_USER
  add constraint APP_USER_PK primary key (USER_ID);

alter table APP_USER
  add constraint APP_USER_UK unique (USER_NAME);


-- Create table
create table APP_ROLE
(
  ROLE_ID   BIGINT not null,
  ROLE_NAME VARCHAR(30) not null
) ;
--  
alter table APP_ROLE
  add constraint APP_ROLE_PK primary key (ROLE_ID);

alter table APP_ROLE
  add constraint APP_ROLE_UK unique (ROLE_NAME);


-- Create table
create table USER_ROLE
(
  ID      BIGINT not null,
  USER_ID BIGINT not null,
  ROLE_ID BIGINT not null
);
--  
alter table USER_ROLE
  add constraint USER_ROLE_PK primary key (ID);

alter table USER_ROLE
  add constraint USER_ROLE_UK unique (USER_ID, ROLE_ID);

alter table USER_ROLE
  add constraint USER_ROLE_FK1 foreign key (USER_ID)
  references APP_USER (USER_ID);

alter table USER_ROLE
  add constraint USER_ROLE_FK2 foreign key (ROLE_ID)
  references APP_ROLE (ROLE_ID);


-- Used by Spring Remember Me API.  
CREATE TABLE Persistent_Logins (

    username varchar(64) not null,
    series varchar(64) not null,
    token varchar(64) not null,
    last_used timestamp not null,
    PRIMARY KEY (series)
    
);

--------------------------------------

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (2, 'dbuser1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (1, 'dbadmin1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

---

insert into app_role (ROLE_ID, ROLE_NAME)
values (1, 'ROLE_ADMIN');

insert into app_role (ROLE_ID, ROLE_NAME)
values (2, 'ROLE_USER');

---

insert into user_role (ID, USER_ID, ROLE_ID)
values (1, 1, 1);

insert into user_role (ID, USER_ID, ROLE_ID)
values (2, 1, 2);

insert into user_role (ID, USER_ID, ROLE_ID)
values (3, 2, 2);
---

SQL Server


-- Create table
create table APP_USER
(
  USER_ID           BIGINT not null,
  USER_NAME         VARCHAR(36) not null,
  ENCRYTED_PASSWORD VARCHAR(128) not null,
  ENABLED           BIT not null  
) ;
--  
alter table APP_USER
  add constraint APP_USER_PK primary key (USER_ID);

alter table APP_USER
  add constraint APP_USER_UK unique (USER_NAME);


-- Create table
create table APP_ROLE
(
  ROLE_ID   BIGINT not null,
  ROLE_NAME VARCHAR(30) not null
) ;
--  
alter table APP_ROLE
  add constraint APP_ROLE_PK primary key (ROLE_ID);

alter table APP_ROLE
  add constraint APP_ROLE_UK unique (ROLE_NAME);


-- Create table
create table USER_ROLE
(
  ID      BIGINT not null,
  USER_ID BIGINT not null,
  ROLE_ID BIGINT not null
);
--  
alter table USER_ROLE
  add constraint USER_ROLE_PK primary key (ID);

alter table USER_ROLE
  add constraint USER_ROLE_UK unique (USER_ID, ROLE_ID);

alter table USER_ROLE
  add constraint USER_ROLE_FK1 foreign key (USER_ID)
  references APP_USER (USER_ID);

alter table USER_ROLE
  add constraint USER_ROLE_FK2 foreign key (ROLE_ID)
  references APP_ROLE (ROLE_ID);

 
    
-- Used by Spring Remember Me API.  
CREATE TABLE Persistent_Logins (

    username varchar(64) not null,
    series varchar(64) not null,
    token varchar(64) not null,
    last_used Datetime not null,
    PRIMARY KEY (series)
    
);

--------------------------------------

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (2, 'dbuser1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (1, 'dbadmin1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

---

insert into app_role (ROLE_ID, ROLE_NAME)
values (1, 'ROLE_ADMIN');

insert into app_role (ROLE_ID, ROLE_NAME)
values (2, 'ROLE_USER');

---

insert into user_role (ID, USER_ID, ROLE_ID)
values (1, 1, 1);

insert into user_role (ID, USER_ID, ROLE_ID)
values (2, 1, 2);

insert into user_role (ID, USER_ID, ROLE_ID)
values (3, 2, 2);

ORACLE


-- Create table
create table APP_USER
(
  USER_ID           NUMBER(19) not null,
  USER_NAME         VARCHAR2(36) not null,
  ENCRYTED_PASSWORD VARCHAR2(128) not null,
  ENABLED           NUMBER(1) not null  
) ;
--  
alter table APP_USER
  add constraint APP_USER_PK primary key (USER_ID);
 
alter table APP_USER
  add constraint APP_USER_UK unique (USER_NAME);


-- Create table
create table APP_ROLE
(
  ROLE_ID   NUMBER(19) not null,
  ROLE_NAME VARCHAR2(30) not null
) ;
--  
alter table APP_ROLE
  add constraint APP_ROLE_PK primary key (ROLE_ID);
 
alter table APP_ROLE
  add constraint APP_ROLE_UK unique (ROLE_NAME);


-- Create table
create table USER_ROLE
(
  ID      NUMBER(19) not null,
  USER_ID NUMBER(19) not null,
  ROLE_ID NUMBER(19) not null
);
--  
alter table USER_ROLE
  add constraint USER_ROLE_PK primary key (ID);
 
alter table USER_ROLE
  add constraint USER_ROLE_UK unique (USER_ID, ROLE_ID);
 
alter table USER_ROLE
  add constraint USER_ROLE_FK1 foreign key (USER_ID)
  references APP_USER (USER_ID);
 
alter table USER_ROLE
  add constraint USER_ROLE_FK2 foreign key (ROLE_ID)
  references APP_ROLE (ROLE_ID);

-- Used by Spring Remember Me API.  
CREATE TABLE Persistent_Logins (

    username varchar2(64) not null,
    series varchar2(64) not null,
    token varchar2(64) not null,
    last_used Date not null,
    PRIMARY KEY (series)
    
);
--------------------------------------

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (2, 'dbuser1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (1, 'dbadmin1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

---

insert into app_role (ROLE_ID, ROLE_NAME)
values (1, 'ROLE_ADMIN');

insert into app_role (ROLE_ID, ROLE_NAME)
values (2, 'ROLE_USER');

---

insert into user_role (ID, USER_ID, ROLE_ID)
values (1, 1, 1);

insert into user_role (ID, USER_ID, ROLE_ID)
values (2, 1, 2);

insert into user_role (ID, USER_ID, ROLE_ID)
values (3, 2, 2);
---
Commit;

Postgres


-- Create table
create table APP_USER
(
  USER_ID           BIGINT not null,
  USER_NAME         VARCHAR(36) not null,
  ENCRYTED_PASSWORD VARCHAR(128) not null,
  ENABLED           Int not null  
) ;
--  
alter table APP_USER
  add constraint APP_USER_PK primary key (USER_ID);

alter table APP_USER
  add constraint APP_USER_UK unique (USER_NAME);


-- Create table
create table APP_ROLE
(
  ROLE_ID   BIGINT not null,
  ROLE_NAME VARCHAR(30) not null
) ;
--  
alter table APP_ROLE
  add constraint APP_ROLE_PK primary key (ROLE_ID);

alter table APP_ROLE
  add constraint APP_ROLE_UK unique (ROLE_NAME);


-- Create table
create table USER_ROLE
(
  ID      BIGINT not null,
  USER_ID BIGINT not null,
  ROLE_ID BIGINT not null
);
--  
alter table USER_ROLE
  add constraint USER_ROLE_PK primary key (ID);

alter table USER_ROLE
  add constraint USER_ROLE_UK unique (USER_ID, ROLE_ID);

alter table USER_ROLE
  add constraint USER_ROLE_FK1 foreign key (USER_ID)
  references APP_USER (USER_ID);

alter table USER_ROLE
  add constraint USER_ROLE_FK2 foreign key (ROLE_ID)
  references APP_ROLE (ROLE_ID);

 
 
-- Used by Spring Remember Me API.  
CREATE TABLE Persistent_Logins (

    username varchar(64) not null,
    series varchar(64) not null,
    token varchar(64) not null,
    last_used timestamp not null,
    PRIMARY KEY (series)
    
);
 
--------------------------------------

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (2, 'dbuser1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

insert into App_User (USER_ID, USER_NAME, ENCRYTED_PASSWORD, ENABLED)
values (1, 'dbadmin1', '$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 1);

---

insert into app_role (ROLE_ID, ROLE_NAME)
values (1, 'ROLE_ADMIN');

insert into app_role (ROLE_ID, ROLE_NAME)
values (2, 'ROLE_USER');

---

insert into user_role (ID, USER_ID, ROLE_ID)
values (1, 1, 1);

insert into user_role (ID, USER_ID, ROLE_ID)
values (2, 1, 2);

insert into user_role (ID, USER_ID, ROLE_ID)
values (3, 2, 2);
---
Commit;

На Eclipse создать проект Spring Boot.

Ввести:

  • Name: SpringBootSecurityJPA
  • Group: org.o7planning
  • Artifact: SpringBootSecurityJPA
  • Description: Spring Boot Spring Security JPA Remember Me.
  • Package: org.o7planning.sbsecurity

Следующий шаг, вам нужно выбрать технологии и библиотеки которые будут использованы ( В данной статье мы подключимся к базе данных Oracle, MySQL, SQL Server или Postgres).

OK Project создан.

Если вы используете базу данных Oracle, вам нужно объявить нужную библиотеку для Oracle в файле pom.xml:

Если вы подключаете к базе данных SQL Service, вы можете использовать одну из 2 библиотек JTDS или Mssql-Jdbc:

Полное содержание файла pom.xml:

Чтобы Spring подключился к базе данных, вам нужно конфигурировать необходимые параметрыв файле application.properties.

Данное приложение имеет некоторые функции (страницы), которые:

Это страница просмотра информации пользователя, данная страница требует пользователя войти в систему, и иметь роль ROLE_ADMIN или ROLE_USER.

Это страница для администратора, требует пользователя войти в систему, и только те что с ролем  ROLE_ADMIN имеют право на доступ.

  • /. /welcome, /login, /logout, /403

Все другие страницы в приложении не требуют пользователя входить в систему.

Класс WebSecurityConfig используется для конфигурации защиты для приложения. Он аннотирован (annotate) с помощью @Configuration, Данная аннотация говорит Spring что он является классом конфигурации, поэтому он будет анализирован с помощью Spring во время запуска данного приложения.

Что такое опция “Remember Me”?

Пользователь заходит на веб страницу и входит в систему. После, пользователь выключает браузер и заходит в вебсайт в определенный момент (Например на другой день), и он должен еще раз войти в систему, это создает ненужную проблему. Опция “Remember Me” позволяет вебсайту “запомнить” информацию пользователя для автоматического входа в систему, когда пользователь заходит в вебсайт в следующий раз.

AppUser.java


package org.o7planning.sbsecurity.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "App_User", //
        uniqueConstraints = { //
                @UniqueConstraint(name = "APP_USER_UK", columnNames = "User_Name") })
public class AppUser {

    @Id
    @GeneratedValue
    @Column(name = "User_Id", nullable = false)
    private Long userId;

    @Column(name = "User_Name", length = 36, nullable = false)
    private String userName;

    @Column(name = "Encryted_Password", length = 128, nullable = false)
    private String encrytedPassword;

    @Column(name = "Enabled", length = 1, nullable = false)
    private boolean enabled;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getEncrytedPassword() {
        return encrytedPassword;
    }

    public void setEncrytedPassword(String encrytedPassword) {
        this.encrytedPassword = encrytedPassword;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

}

Классы DAO (Data Access Object) являются классами использующиеся для доступа в базу данных, например Query, Insert, Update, Delete. Классы DAO обычно аннотированыы с помощью @Repository чтобы сказать Spring управлять ими как Spring BEAN.

Класс AppUserDAO используется для манипуляции с таблицей APP_USER. Он имеет метод поиска пользователя в базе данных соответствующего имени пользователя.

UserDetailsService это центральный интерфейс в Spring Security. Это сервис для поиска “Аккаунт пользователя и роли того пользователя”. Используется Spring Security каждый раз, когда пользователь входит в систему. Поэтому вам нужно написать класс применения (implements) данного интерфейса.

Здесь я создаю класс UserDetailsServiceImpl применения (implements) интерфейса UserDetailsService. Класс UserDetailsServiceImpl аннотирован с помощью @Service чтобы сказать Spring управлять им как Spring BEAN.     

MainController.java


package org.o7planning.sbsecurity.controller;

import java.security.Principal;

import org.o7planning.sbsecurity.utils.WebUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MainController {

	@RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)
	public String welcomePage(Model model) {
		model.addAttribute("title", "Welcome");
		model.addAttribute("message", "This is welcome page!");
		return "welcomePage";
	}

	@RequestMapping(value = "/admin", method = RequestMethod.GET)
	public String adminPage(Model model, Principal principal) {
		
		User loginedUser = (User) ((Authentication) principal).getPrincipal();

		String userInfo = WebUtils.toString(loginedUser);
		model.addAttribute("userInfo", userInfo);
		
		return "adminPage";
	}

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String loginPage(Model model) {

		return "loginPage";
	}

	@RequestMapping(value = "/logoutSuccessful", method = RequestMethod.GET)
	public String logoutSuccessfulPage(Model model) {
		model.addAttribute("title", "Logout");
		return "logoutSuccessfulPage";
	}

	@RequestMapping(value = "/userInfo", method = RequestMethod.GET)
	public String userInfo(Model model, Principal principal) {

		// After user login successfully.
		String userName = principal.getName();

		System.out.println("User Name: "   userName);

		User loginedUser = (User) ((Authentication) principal).getPrincipal();

		String userInfo = WebUtils.toString(loginedUser);
		model.addAttribute("userInfo", userInfo);

		return "userInfoPage";
	}

	@RequestMapping(value = "/403", method = RequestMethod.GET)
	public String accessDenied(Model model, Principal principal) {

		if (principal != null) {
			User loginedUser = (User) ((Authentication) principal).getPrincipal();

			String userInfo = WebUtils.toString(loginedUser);

			model.addAttribute("userInfo", userInfo);

			String message = "Hi "   principal.getName() //
					  "<br> You do not have permission to access this page!";
			model.addAttribute("message", message);

		}

		return "403Page";
	}

}

_menu.html используется как часть веб страницы, он погружен в другие страницы, чтобы создать Menu страницы.

Если пользователь вошел в систему, но доступ в страницу не авторизирован (Не входит в их роль), система отобразит содержание страницы /403 для оповещения запрета доступа в страницу (Access Denied).

Нажать на правую кнопку мыши на Project, выбрать:

Перейти по адресу:

Войти в систему с ролью ROLE_USER:

Войти в систему с ролью​​​​​​​ ROLE_ADMIN:

Тест функции “Remember Me”.

Смотреть данные в таблице PERSISTENT_LOGINS:

Создание jwt при успешной авторизации

Первым делом мы обновим конечную точку авторизации, чтобы при успешном входе в систему она генерировала JWT. Для простоты мы используем сохранение JWT в куках, но с точки зрения безопасности есть и лучшие подходы. Эта тема углублённо развёрнута в статье “Полное руководство по управлению JWT во фронтенд клиентах.” .

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

В качестве библиотеки JWT я собираюсь использовать JJWT, но любая другая эквивалентная библиотека тоже вполне подойдёт.

implementation("io.jsonwebtoken:jjwt-api:0.11.1")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.1")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.1")

Теперь мы можем создать базовый класс JwSigner, использующийся для генерации при запуске пары ключей (keypair), которая, в свою очередь, создаёт JWT-токены.

JwtSigner, ответственный за создание и проверку токенов:

Заключение

В этой статье рассмотрели один из примеров реализации REST приложения с Spring Security и JWT. Надеюсь данный вариант реализации кому то окажется полезным.

Полный код проекта выложен доступен на github

Похожее:  Esplus kvp24 ru личный кабинет

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *