Spring Boot 2.X Combat - вход и регистрация в Spring Security

Spring Boot

Автор: Сяосянь

Репозиторий исходного кода:GitHub.com/Округ Чжашуй/…

Для веб-системы необходимо контролировать права доступа к страницам и API-интерфейсам, например, необходимо запретить доступ несистемным пользователям и контролировать права доступа разных страниц или интерфейсов. В разработке Java обычно используются среды безопасности Spring Security и Apache Shiro.

Spring Security — это инфраструктура безопасности экосистемы Spring, реализованная на основе фильтров Spring AOP и сервлетов и рекомендованная Spring Boot. Он обеспечивает комплексное решение для обеспечения безопасности при обработке аутентификации и авторизации как на уровне веб-запросов, так и на уровне вызова метода.

Spring Security в основном включает следующие две части:

  • Авторизация при входе (Аутентификация)
  • Авторизация

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

База данных использует MySQL, а структура уровня сохраняемости данных использует MyBatis.

Spring Security использует сеанс по умолчанию, поэтому он не использует RESTful API и использует шаблон MVC. Thymeleaf как шаблонизатор для веб-страниц

1) Введение зависимости и конфигурация проекта

Новый проект 05-весна-безопасность,Обратите внимание, что версия Spring Boot должна быть версии 2.1.X.

1.1) Введение зависимости

Starter (стартер), предоставляемый Spring Security для Spring Boot, позволяет Spring Boot интегрировать Security практически без разработки конфигурации.

thymeleaf-extras-springsecurity5 — это расширение Thymeleaf для управления отображением веб-элементов на веб-страницах.

Зависимости проекта Gradle

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    // https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5
    compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5', version: '3.0.4.RELEASE'

См. нижнюю часть статьи о зависимостях проекта Maven.

1.2) Конфигурация проекта

Настройте базу данных MySQL и преобразование верблюжьего регистра MyBatis, application.properties

# 数据库 URL、用户名、密码、JDBC Driver更换数据库只需更改这些信息即可
# MySQL 8 需要指定 serverTimezone 才能连接成功
spring.datasource.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.password=xiaoxian
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis 驼峰命名转换
mybatis.configuration.map-underscore-to-camel-case=true

Добавить @MapperScan

@MapperScan("org.xian.security.mapper")
public class SecurityApplication {}

2) Начните работу с Spring Security

В проекте используется трехуровневая модель, подобная MVC.

image-20200325231257474

Просмотр уровня представления: Веб-страница, созданная Thymeleaf.Контроллер: Основная логическая часть приложения.Слой модели модели: Напишите соответствующий интерфейс MyBatis Mapper для взаимодействия с базой данных MySQL.

2.1) Структура таблицы данных и класс объектов Mapper

Создайте следующую пользовательскую таблицу sys_user

поле тип Примечание
user_id bigint автоматическое увеличение первичного ключа
username varchar(18) Имя пользователя, ненулевое и уникальное
password varchar(60) пароль, не пустой
user_role varchar(8) Роли пользователей (ПОЛЬЗОВАТЕЛЬ / АДМИНИСТРАТОР)

Роли пользователей здесь USER/ADMIN, ситуация, когда у пользователя может быть несколько ролей, пока не рассматривается.

SQL

use spring;
create table sys_user
(
    user_id   bigint auto_increment,
    username  varchar(18)  not null unique,
    password  varchar(60) not null,
    user_role varchar(8)   not null,
    constraint sys_user_pk
        primary key (user_id)
);

Класс сущностей картографа: Создайте новый пакет с именем entity . Создайте новый класс SysUser под сущностью:

public class SysUser implements Serializable {
    private static final long serialVersionUID = 4522943071576672084L;
    private Long userId;
    private String username;
    private String password;
    private String userRole;
    // 省略 getter setter constructor
}

2.2) Интерфейс картографа

// 这里使用注解的方式
public interface SysUserMapper {
    /** 往 sys_user 插入一条记录
     * @param sysUser 用户信息
     */
    @Insert("Insert Into sys_user(username, password,user_role) Values(#{username}, #{password},#{userRole})")
    @Options(useGeneratedKeys = true, keyProperty = "userId")
    void insert(SysUser sysUser);

    /** 根据用户 Username 查询用户信息
     * @param username 用户名
     * @return 用户信息
     */
    @Select("Select user_id,username, password,user_role From sys_user Where username=#{username}")
    SysUser selectByUsername(String username);
}

2.3) Конфигурация безопасности Spring

С Spring Security вам нужно только реализовать интерфейс UserDetailsService и наследовать от WebSecurityConfigurerAdapter.

Создайте новый пакет безопасности, создайте новый класс MyUserDetailsServiceImpl, SpringSecurityConfig

Реализовать службу UserDetails

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private SysUserMapper sysUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserMapper.selectByUsername(username);
        if (null == sysUser) {
            throw new UsernameNotFoundException(username);
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_" + sysUser.getUserRole()));
        return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
    }
}

Анализ кода:

loadUserByUsername: переопределив метод loadUserByUsername интерфейса UserDetailsService, передайте имя пользователя, пароль пользователя и роль пользователя в Spring Security.

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

"ROLE_" + sysUser.getUserRole(): Имена ролей Spring Security по умолчанию начинаются с «ROLE_».

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

Класс SpringSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyUserDetailsServiceImpl userDetailsService;

    @Override
    public void configure(WebSecurity web) {
        // 忽略前端静态资源 css js 等
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/js/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 设置密码加密方式,验证密码的在这里
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用 BCryptPasswordEncoder
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 允许无授权访问 "/login"、"/register" "/register-save"
        // 其他地址的访问均需验证权限
        http.authorizeRequests()
                .antMatchers("/login", "/register", "/register-save", "/error").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                // 用户名和用户密码参数名称
                .passwordParameter("password") 
                .usernameParameter("username")
                // 指定登录页面
                .loginPage("/login")
                // 登录错误跳转到 /login-error
                .failureUrl("/login-error")
                .permitAll()
                .and()
                // 设置退出登录的 URL 和退出成功后跳转页面
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login");
    }
}

Разбор кода:

BCryptPasswordEncoder: хранить пароль в зашифрованном виде с помощью надежного хэш-метода BCrypt. Для веб-систем пароли редко хранятся в открытом виде. Класс BCryptPasswordEncoder, предоставляемый Spring Security, реализует функцию шифрования паролей. Сильный метод хэширования BCrypt каждый раз приводит к разному шифрованию.

2.3) Реализовать функцию регистрации пользователей

В resources/templates/ создайте новый register.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<p>注册页面</p>
<p th:if="${error}" class="error">注册错误</p>
<form th:action="@{/register-save}" method="post">
    <label for="username">用户名:</label>:
    <input type="text" id="username" name="username" autofocus="autofocus"/> <br/>
    <label for="password">密码:</label>:
    <input type="password" id="password" name="password"/> <br/>
    <label for="userRole">用户角色</label>:
    <select name="userRole" id="userRole">
        <option value="ADMIN">管理员</option>
        <option value="USER">普通用户</option>
    </select>
    <br/>
    <input type="submit" value="注册"/><br/>
    <a href="index.html" th:href="@{/}">返回首页</a> <br/>
    <a href="login.html" th:href="@{/login}">登录</a>
</form>
</body>
</html>

Анализ кода:

Тег в начале th: указывает на рендеринг Thymeleaf. th:if означает суждение, URL-адрес th:action отправляет путь, дополнительные функции Thymeleaf можно найти на официальном сайте.www.thymeleaf.org/

Создайте новый пакет контроллера и создайте в нем новый класс RegisterController:

@Controller
public class RegisterController {
    @Resource
    private SysUserMapper sysUserMapper;

    @RequestMapping("/register")
    public String register() {
        return "register";
    }

    @RequestMapping("/register-error")
    public String registerError(Model model) {
        // Model 的作用是往 Web 页面穿数据
        // model 添加一个参数 error 其作用是如果此参数为 true,就显示下面一行 HTML 代码
        // <p th:if="${error}" class="error">注册错误</p>
        model.addAttribute("error", true);
        return "register";
    }

    @RequestMapping("/register-save")
    public String registerSave(@ModelAttribute SysUser sysUser,
                               Model model) {
        // 判断 username password 不能为空
        if (sysUser.getUsername() == null || sysUser.getPassword() == null || sysUser.getUserRole() == null) {
            model.addAttribute("error", true);
            return "register";
        }
        try {
            // 密码加密存储
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
            String password = bCryptPasswordEncoder.encode(sysUser.getPassword());
            sysUser.setPassword(password);
            // 写入数据库
            sysUserMapper.insert(sysUser);
            //  重定向到 login 页面
            return "redirect:/login";
        } catch (Exception e) {
            // 注册错误
            model.addAttribute("error", true);
            return "register";
        }
    }
}

Разбор кода:

@ControllerАннотация указывает, что возвращается HTML-страница. return "register" означает возврат register.html, return "redirect:/xxx" перенаправляет на определенную страницу.

@ModelAttribute SysUser sysUser: Аналогично @RequestBody, считывает данные из формы и назначает их SysUser.

Запустите проект и получите к нему доступ через браузерhttp://localhost:8080/register, введите имя пользователя и пароль, выберите роль пользователя и нажмите «Зарегистрироваться».

Не забудьте добавить параметр запуска -Djava.security.egd=file:/dev/./urandom JVM, который кажется ошибкой в ​​случайном числе Security.

image-20200326002456189

2.4) Реализовать функцию входа пользователя

Создайте новые login.html и index.html (в разделе 2.6).

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<p>登录</p>
<p th:if="${error}" class="error">用户名或密码错误</p>
<form th:action="@{/login}" method="post">
    <label for="username">用户名</label>:
    <input type="text" id="username" name="username" autofocus="autofocus"/> <br/>
    <label for="password">用户密码</label>:
    <input type="password" id="password" name="password"/> <br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>

Создайте новый класс LoginController в пакете контроллера:

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/login-error")
    public String loginError(Model model) {
        // 登录错误
        model.addAttribute("error", true);
        return "login";
    }
}

Поскольку мы указали роли двух путей /login и /login-error, Spring Security реализует конкретную реализацию входа в систему и ошибки входа в систему.

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

2.5) Контроль разрешений роли пользователя

Контроль разрешений роли пользователя, который может быть аннотирован в @RequestMapping через @PreAuthorize, указывающий, что для этого URL-адреса требуется определенное разрешение роли; другой — реализовать некоторые элементы страницы через Thymeleaf, что требует указания разрешения роли для доступа.

Вот первый способ ввести аннотацию @PreAuthorize:

Создайте новый AdminController в пакете контроллера, и соответствующий admin.html сможет просматривать исходный код хранилища:

@Controller
public class AdminController {
    // 需要 ROLE_ADMIN 角色才行访问 /admin
    // 这也是为什么 MyUserDetailsServiceImpl 需要 "ROLE_" + sysUser.getUserRole()
    @PreAuthorize("hasRole('ADMIN')")
    @RequestMapping("/admin")
    public String admin() {
        return "admin";
    }
}

Перезапустите проект, войдите с разными ролями USER, ADMIN, доступhttp://локальный:8080/админ. Просмотр текущих результатов различных разрешений роли.

2.6) Управление персонажем Thymeleaf

На одной и той же странице некоторые элементы могут быть видны АДМИНИСТРАТОРУ, а некоторые — ПОЛЬЗОВАТЕЛЮ. Они реализованы через плагин расширения Thymeleaf thymeleaf-extras-springsecurity.

Измените страницу index.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<h1>登录成功</h1>
<div sec:authorize="isAuthenticated()">
    登录的用户都可以见
</div>
<br/>
<div sec:authorize="hasRole('ROLE_ADMIN')">
    管理员才可以看见 <br/>
    <a href="admin.html" th:href="@{/admin}">管理页</a>
</div>
<br/>
<div sec:authorize="hasRole('ROLE_USER')">
    用户才可以看见
</div>
<br/>
<br/>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="退出登录"/>
</form>
</body>
</html>

приложение

Зависимости проекта Maven

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
  <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
  </dependency>
</dependencies>

Этот раздел в основном посвящен интеграции Spring Boot с Spring Security для реализации регистрации пользователей, входа в систему и управления ролями. В следующем разделе мы интегрируем JJWT с Spring Boot, чтобы реализовать интерфейс API RESTful для аутентификации и авторизации токена.