原文链接: Spring Security Login Form Example with Database Authentication – 原文作者: Ramesh Fadatare
本文选用的是意译的方法
译者加:上一篇文章 Spring Security 自定义登陆页面 咱们根据
InMemoryUserDetailsManager
进行用户认证。这篇文章,咱们将集合数据库mysql
。
在这篇 Spring Security
文章中,咱们将学习怎样运用 Spring Security
和 MySQL
数据库进行数据库认证,并使用在自定义的登陆表单中。
在这个数据库认证事例中,用户在登陆的表单输入登陆凭据,比如用户名和暗码,然后点击登陆。接着,咱们在数据库表单中对用户输入的凭据,即用户名和暗码进行验证。
数据库设置
如图:
添加 Maven 依靠
在你的 Spring Boot
项目中,添加下面的 Maven
依靠:
<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
服务器中创立一个数据库:
create database login_system
由于咱们运用 MySQL
作为咱们的数据库,所以咱们需要装备数据库的 URL, username 和 password,以便在数据库启动时 Spring
能够和它树立联系。
在 src/main/resources/application.properties
文件中,添加下面的特点:
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
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
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
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
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()
方法:
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
的类,如下装备:
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
<!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
方法来烘托登陆模版:
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
一旦用户成功登陆,那么欢迎页面将会展现出来:
<!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
脚本在数据库县相关表中刺进数据:
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
数据库常识使用到自定义的登陆表单上。