编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Spring Security 登陆表单案例,结合数据库认证

wxchong 2024-08-09 11:20:55 开源技术 12 ℃ 0 评论

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

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

数据库设置

如图:

添加 Maven 依赖

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

xml

复制代码

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <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>

译者加: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<Role> 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<User, Long> { Optional<User> findByUsername(String username); Boolean existsByEmail(String email); Optional<User> 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<Role, Long> { Optional<Role> 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<GrantedAuthority> 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

复制代码

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" > <head> <meta charset="UTF-8"> <title>Login System</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container-fluid"> <a class="navbar-brand" th:href="@{/index}">Spring Security Custom Login Example</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> </div> </nav> <br /><br /> <div class="container"> <div class="row"> <div class="col-md-6 offset-md-3"> <div th:if="${param.error}"> <div class="alert alert-danger">Invalid Email or Password</div> </div> <div th:if="${param.logout}"> <div class="alert alert-success"> You have been logged out.</div> </div> <div class="card"> <div class="card-header"> <h2 class="text-center">Login Form</h2> </div> <div class="card-body"> <form method="post" role="form" th:action="@{/login}" class="form-horizontal" > <div class="form-group mb-3"> <label class="control-label"> Email</label> <input type="text" id="username" name="username" class="form-control" placeholder="Enter email address" /> </div> <div class="form-group mb-3"> <label class="control-label"> Password</label> <input type="password" id="password" name="password" class="form-control" placeholder="Enter password" /> </div> <div class="form-group mb-3"> <button type="submit" class="btn btn-primary" >Submit</button> </div> </form> </div> </div> </div> </div> </div> </body> </html>

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

  • 表单应该触发 /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

复制代码

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" > <head> <meta charset="UTF-8"> <title>Registration and Login System</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container-fluid"> <a class="navbar-brand" th:href="@{/index}">Spring Security Custom Login Example</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link active" aria-current="page" th:href="@{/logout}">Logout</a> </li> </ul> </div> </div> </nav> <br /><br /> <body> <div class="container"> <div class="row"> <h1> Welcome to Spring Security world!</h1> </div> </div> </body> </html>

插入 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 数据库知识应用到自定义的登陆表单上。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表