- Spring Security 教程
- Spring Security - 首页
- Spring Security - 简介
- Spring Security - 架构
- Spring Security - 项目模块
- Spring Security - 环境设置
- Spring Security - 表单登录
- Spring Security - 自定义表单登录
- Spring Security - 注销
- Spring Security - 记住我
- Spring Security - 重定向
- Spring Security - 标签库
- Spring Security - XML 配置
- Spring Security - 认证提供程序
- Spring Security - 基本认证
- Spring Security - AuthenticationFailureHandler
- Spring Security - JWT
- Spring Security - 获取用户信息
- Spring Security - Maven
- Spring Security - 默认密码编码器
- Spring Security – 密码编码
- Spring Security - 方法级别
- Spring Security 有用资源
- Spring Security - 快速指南
- Spring Security - 有用资源
- Spring Security - 讨论
Spring Security - 自定义表单登录
基于表单的登录是 Spring Security 提供支持的一种用户名/密码身份验证形式。这通过一个 HTML 表单提供。
每当用户请求受保护的资源时,Spring Security 都会检查请求的身份验证。如果请求未经身份验证/授权,则用户将被重定向到登录页面。登录页面必须以某种方式由应用程序呈现。Spring Security 默认情况下提供该登录表单,正如我们在Spring Security - 表单登录章节中看到的。
在大多数生产案例中,登录页面都是自定义的,并且必须像下面这样显式提供 -
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeHttpRequests(
request -> request.requestMatchers("/login").permitAll()
.requestMatchers("/**").authenticated()
)
.formLogin(form -> form.loginPage("/login").permitAll())
}
让我们开始使用 Spring Security 进行实际编程。在开始使用 Spring 框架编写示例之前,您必须确保已正确设置 Spring 环境,如Spring Security - 环境设置章节中所述。我们还假设您对 Spring Tool Suite IDE 有基本的了解。
现在让我们继续编写一个由 Maven 管理的基于 Spring MVC 的应用程序,该应用程序将要求用户登录、对用户进行身份验证,然后使用 Spring Security 表单登录功能提供注销选项。
使用 Spring Initializr 创建项目
Spring Initializr 是开始 Spring Boot 项目的好方法。它提供了一个易于使用的用户界面来创建项目、添加依赖项、选择 Java 运行时等。它生成一个骨架项目结构,一旦下载,就可以导入到 Spring Tool Suite 中,然后我们可以继续使用我们的现成项目结构。
我们选择了一个 Maven 项目,将项目命名为 formlogin,Java 版本为 21。添加了以下依赖项
Spring Web
Spring Security
Spring Boot DevTools
Thymeleaf 是 Java 的模板引擎。它允许我们快速开发静态或动态网页以在浏览器中呈现。它具有极强的可扩展性,允许我们详细定义和自定义模板的处理。此外,我们可以通过点击此链接了解更多关于 Thymeleaf 的信息。
让我们继续生成我们的项目并下载它。然后我们将它解压缩到我们选择的文件夹中,并使用任何 IDE 打开它。我将使用Spring Tools Suite 4。它可以从https://springframework.org.cn/tools网站免费下载,并且针对 Spring 应用程序进行了优化。
包含所有相关依赖项的 pom.xml
让我们看看我们的 pom.xml 文件。它应该看起来类似于以下内容 -
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tutorialspoint.security</groupId>
<artifactId>formlogin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>formlogin</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<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.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Login.html
此代码需要在映射的文件夹中存在一个 login.html 文件,该文件将在访问 /login 时返回。此 HTML 文件应包含一个登录表单。此外,请求应为 POST 请求到 /login。参数名称应分别为“username”和“password”以表示用户名和密码。
在/src/main/resources/templates文件夹中创建 login.html,内容如下,用作登录页面。
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">
<p>Bad Credentials</p>
</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<h1>Please sign in</h1>
<label for="username"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" id="username" required>
<label for="password"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" id="password" required>
<input type="submit" value="Sign In" />
</div>
</form>
</body>
</html>
更新 Spring Security 配置类
在我们的 config 包中,我们有 WebSecurityConfig 类,如Spring Security - 表单登录章节中所定义。让我们更新其 filterChain() 方法以用于我们的自定义登录页面
http.
//...
.formLogin(form -> form.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error=true")
.permitAll())
//...
.build();
以下是 Spring Security 配置类的完整代码
WebSecurityConfig.java
package com.tutorialspoint.security.formlogin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
protected UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("user123"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin123"))
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
request -> request.requestMatchers("/login").permitAll()
.requestMatchers("/**").authenticated()
)
.formLogin(form -> form.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error=true")
.permitAll())
.logout(config -> config
.logoutUrl("/logout")
.logoutSuccessUrl("/login"))
.build();
}
}
这里有几点需要注意 -
defaultSuccessUrl ("/") − 此端点将作为我们的应用程序的首页和成功页面。正如我们之前配置的那样,我们将保护此页面,并且只有经过身份验证的用户才能访问此页面。
failureUrl ("/login?error=true") − 此端点将加载带有错误标志的登录页面以显示错误消息。
logoutUrl ("/logout") − 这将用于注销我们的应用程序。
logoutSuccessUrl ("/login") − 这将用于在用户成功注销后加载登录页面。
控制器类
在这个类中,我们为“/”端点和“/login”端点创建了映射,分别用于此应用程序的首页和登录页面。
AuthController.java
package com.tutorialspoint.security.formlogin.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class AuthController {
@GetMapping("/")
public String home() {
return "index";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
视图
让我们更新/src/main/resources/templates文件夹中的 index.html,内容如下,用作首页并显示已登录的用户名。
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<title>
Hello World!
</title>
</head>
<body>
<h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
<html>
login.html
让我们在/src/main/resources/templates文件夹中创建 login.html,内容如下,用作登录页面。我们使用默认名称username和password作为文本字段。如果使用其他名称,我们也需要在 Spring Security 配置类中设置相同的名称。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">
<p>Bad Credentials</p>
</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<h1>Please sign in</h1>
<label for="username">
<b>Username</b>
</label>
<input type="text" placeholder="Enter Username" name="username" id="username" required>
<label for="password"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" id="password" required>
<input type="submit" value="Sign In" />
</div>
</form>
</body>
</html>
在 login.html 中,我们使用${param.error}读取请求参数error。如果为真,则打印错误消息Bad Credential。类似地,我们使用${param.logout}读取请求参数logout。如果为真,则打印注销消息。
在登录表单中,我们使用 POST 方法登录,同时使用名称和 ID 分别为username和password的输入字段。
运行应用程序
由于我们已经准备好所有组件,让我们运行应用程序。右键单击项目,选择Run As,然后选择Spring Boot App。
它将启动应用程序,并且应用程序启动后,我们可以运行 localhost:8080 以检查更改。
输出
现在打开 localhost:8080,您可以看到我们的自定义登录页面。
登录页面
错误凭据的登录页面
输入任何无效的凭据,它将显示错误。
主页
输入有效的凭据
它将加载主页。
注销后
现在单击注销按钮,这将再次加载登录页面。