在这篇 Spring Security 文章中,我们将学习怎么使用 Spring Security 和 MySQL 数据库进行数据库认证,并应用在自定义的登陆表单中。

在这个数据库认证案例中,用户在登陆的表单输入登陆凭证,比如用户名和密码,然后点击登陆。接着,我们在数据库表单中对用户输入的凭证,即用户名和密码进行验证。

数据库设置

如图:

添加 Maven 依赖

在你的 Spring Boot 项目中,添加下面的 Maven 依赖:

xml

复制代码

org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-thymeleaf

译者加:Lombok 的主要作用是减少重复劳动和简化代码。通过使用 Lombok 注解,开发人员可以自动添加生成 getter 和 setter 方法、equals()、toString() 等常见的样板代码。

配置 MySQL 数据库

首先,我们使用下面的命令行在 MySQL 服务器中创建一个数据库:

bash

复制代码

create database login_system

因为我们使用 MySQL 作为我们的数据库,所以我们需要配置数据库的 URL, username 和 password,以便在数据库启动时 Spring 可以和它建立联系。


src/main/resources/application.properties 文件中,添加下面的属性:

bash

复制代码

spring.datasource.url = jdbc:mysql://localhost:3306/login_system spring.datasource.username = root spring.datasource.password = root # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto = update logging.level.org.springframework.security=DEBUG

Model 层 - 创建 JPA 实体

在这个章节中,我们将创建 User 和 Role 的 JPA 实体,然后让它们之间建立多对多(MANY-to-MANY)的关系。让我们使用 JPA 的注解在 User 和 Role 实体中建立多对多的关系

User

java

复制代码

import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.Set; @Setter @Getter @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @Column(nullable = false, unique = true) private String username; @Column(nullable = false, unique = true) private String email; @Column(nullable = false) 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 Set roles; }

Role

java

复制代码

import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; }

Repository 层

UserRepository

java

复制代码

import net.javaguides.todo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); Boolean existsByEmail(String email); Optional findByUsernameOrEmail(String username, String email); boolean existsByUsername(String username); }

RoleRepository

java

复制代码

import net.javaguides.todo.entity.Role; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Map; import java.util.Optional; public interface RoleRepository extends JpaRepository { Optional findByName(String name); }

Service 层 - CustomUserDetailsService

让我们添加个逻辑,从数据库中通过 name 或者 email 加载用户详情。

我们创建了一个 CustomUserDetailsService,它实现了 UserDetailsService 接口(Spring Security 内置的接口),并提供了一个实现了的 loadUserByUsername() 方法:

java

复制代码

import lombok.AllArgsConstructor; import net.javaguides.todo.entity.User; import net.javaguides.todo.repository.UserRepository; 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 java.util.Set; import java.util.stream.Collectors; @Service @AllArgsConstructor public class CustomUserDetailsService implements UserDetailsService { private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException { User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail) .orElseThrow(() -> new UsernameNotFoundException("User not exists by Username or Email")); Set authorities = user.getRoles().stream() .map((role) -> new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toSet()); return new org.springframework.security.core.userdetails.User( usernameOrEmail, user.getPassword(), authorities ); } }

Spring Security 使用 UserDetailsService 接口,包含了loadUserByUsername(String username) 方法,通过 username 来查询 UserDetails 信息。

UserDetails 接口代表一个认证的用户对象,Spring Security 提供了
org.springframework.security.core.userdetails.User 的开箱即用的实现。

Spring Security 配置

让我们创建一个名为 SpringSecurityConfig 的类,如下配置:

java

复制代码

import lombok.AllArgsConstructor; 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.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @AllArgsConstructor public class SpringSecurityConfig { private UserDetailsService userDetailsService; @Bean public static PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ).formLogin( form -> form .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessUrl("/welcome") .permitAll() ).logout( logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .permitAll() ); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } }

当我们在 Spring Security 配置中指定登陆的页面,我们就应该编写登陆页面。我们将使用 Spring Security 提供的 BCryptPasswordEncoder 类去加密密码。

Thymeleaf Template - 自定义登陆页面

下面这个 Thymeleaf 模版将产生一个符合 /login 登陆页面的 HTML 登陆表单。

Login Form - src/main/resources/templates/login.html

ini

复制代码

Spring Security Custom Login Example 

Invalid Email or Password

You have been logged out.

Login Form

Email 

Password 

Submit

关于这个自定义登陆表单,有几个关键点,如下:

  • 表单应该触发 /login 的 post 接口
  • 表单应该在参数中指定名为 username 的用户名
  • 表单应该在参数中指定名为 password 的密码
  • 如果 HTTP 参数名为 error 出现,则表明用户提供的用户名或者密码无效
  • 如果 HTTP 参数名为 logout 出现,则表明用户成功退出
  • 很多用户不需要太多自定义用户登陆页面。然而,如果需要,我们可以使用额外的配置自定义想要的内容

Spring MVC Controller

让我们在 Spring MVC 中创建一个 /login 的 GET 方法来渲染登陆模版:

java

复制代码

import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class WelComeController { @GetMapping("/welcome") public String greeting() { return "welcome"; } @GetMapping("/login") public String login(){ return "login"; } }

当然,我们也需要创建一个方法来处理 welcome 的 Thymeleaf 模版页面。

Thymeleaf 模版 - welcome.html

一旦用户成功登陆,那么欢迎页面将会展示出来:

html

复制代码

Spring Security Custom Login Example

  •  Logout

 Welcome to Spring Security world!

插入 SQL 脚本

在测试 Spring Security 之前,我们确保使用下面的 SQL 脚本在数据库县相关表中插入数据:

bash

复制代码

INSERT INTO `users` VALUES (1,'ramesh@gmail.com','ramesh','$2a$10$5PiyN0MsG0y886d8xWXtwuLXK0Y7zZwcN5xm82b4oDSVr7yF0O6em','ramesh'), (2,'admin@gmail.com','admin','$2a$10$gqHrslMttQWSsDSVRTK1OehkkBiXsJ/a4z2OURU./dizwOQu5Lovu','admin'); INSERT INTO `roles` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'); INSERT INTO `users_roles` VALUES (2,1),(1,2);

Hibernate 将自动创建创建数据库表,所以你不需要手动创建表。

使用浏览器测试数据库鉴权的用户登陆

在浏览器中输入 URL 为 http://localhost:8080 以导航到登陆页面。接着,输入用户名/密码为 admin/admin,然后点击提交按钮:

登陆成功后,我们会看到下面的网页:

接着,点击退出按钮退出应用:

总结

在这个 Spring Security 教程中,我们学到了怎么将 Spring Security 和 MYSQL 数据库知识应用到自定义的登陆表单上。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐