# 表单登录

Spring 安全性为正在通过 HTML 表单提供的用户名和密码提供支持。本节详细介绍了 Spring 安全性中基于表单的身份验证的工作方式。

让我们来看看基于表单的登录在 Spring 安全性中是如何工作的。首先，我们来看看用户是如何被重定向到 Log In 表单的。

![LoginurlauthenticationEntryPoint ](../../../_images/servlet/authentication/unpwd/loginurlauthenticationentrypoint.png)

图 1。重定向到登录页面

该图构建于我们的[`SecurityFilterChain`]（../../architecture.html# Servlet-SecurityFilterchain）图。

![number 1](../../../_images/icons/number_1.png)首先，用户向资源`/private`发出未经授权的请求。

![number 2](../../../_images/icons/number_2.png) Spring security 的[`FilterSecurityInterceptor`]（.../授权/authorization/authorization/authorization-requests.html# Servlet-authorization-filtersecurityinterceptor）通过抛出`AccessDeniedException`表示未经验证的请求是*拒绝*。

![number 3](../../../_images/icons/number_3.png)由于未对用户进行身份验证，[`ExceptionTranslationFilter`]（..../architecture.html# Servlet-ExceptionTranslationFilter）启动*启动身份验证*，并用配置的[`AuthenticationEntryPoint`]（../architecture.html# Servlet-authentication-authentrationEntryPoint）向登录页面发送重定向。在大多数情况下，`AuthenticationEntryPoint`是[`LoginUrlAuthenticationEntryPoint`]（https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/authentication/loginurlauthenticationentrypoint.html）的一个实例。

![number 4](../../../_images/icons/number_4.png)然后浏览器将请求重定向到的登录页面。

![number 5](../../../_images/icons/number_5.png)应用程序中的某个内容，必须[呈现登录页面](#servlet-authentication-form-custom)。

当提交用户名和密码时，`UsernamePasswordAuthenticationFilter`将对用户名和密码进行身份验证。`UsernamePasswordAuthenticationFilter`扩展了[抽象处理过滤器](../architecture.html#servlet-authentication-abstractprocessingfilter)，所以这个图看起来应该很相似。

![用户名 passwordauthenticationfilter ](../../../_images/servlet/authentication/unpwd/usernamepasswordauthenticationfilter.png)

图 2。验证用户名和密码

该图构建于我们的[`SecurityFilterChain`]（../../architecture.html# Servlet-SecurityFilterchain）图。

![number 1](../../../_images/icons/number_1.png)当用户提交他们的用户名和密码时，`UsernamePasswordAuthenticationFilter`通过从`HttpServletRequest`中提取用户名和密码，创建一个`UsernamePasswordAuthenticationToken`，这是一种[`Authentication`]（../architecture.html# Servlet-authentication-authentication）的类型。

![number 2](../../../_images/icons/number_2.png)接下来，将`UsernamePasswordAuthenticationToken`传递到`AuthenticationManager`中以进行身份验证。`AuthenticationManager`的详细内容取决于[用户信息被存储](index.html#servlet-authentication-unpwd-storage)的方式。

![number 3](../../../_images/icons/number_3.png)如果身份验证失败，则*失败*

* [SecurityContextholder ](../architecture.html#servlet-authentication-securitycontextholder)被清除。

* 调用`RememberMeServices.loginFail`。如果 Remember Me 没有配置，这是一个禁止操作。

* 调用`AuthenticationFailureHandler`。

![number 4](../../../_images/icons/number_4.png)如果身份验证成功，则*成功*。

* `SessionAuthenticationStrategy`被通知有一个新的登录。

* [认证](../architecture.html#servlet-authentication-authentication)设置在[SecurityContextholder ](../architecture.html#servlet-authentication-securitycontextholder)上。

* 调用`RememberMeServices.loginSuccess`。如果 Remember Me 没有配置，这是一个禁止操作。

* `ApplicationEventPublisher`发布`InteractiveAuthenticationSuccessEvent`。

* 调用`AuthenticationSuccessHandler`。通常这是一个`SimpleUrlAuthenticationSuccessHandler`，当我们重定向到登录页面时，它将重定向到由[`ExceptionTranslationFilter`]（../../architecture.html# Servlet-ExceptionTranslationFilter）保存的请求。

Spring 默认情况下启用安全表单登录。然而，只要提供了任何基于 Servlet 的配置，就必须显式地提供基于表单的登录。可以在下面找到最小的、显式的 爪哇 配置：

例 1。表单登录

爪哇

```
protected void configure(HttpSecurity http) {
	http
		// ...
		.formLogin(withDefaults());
}
```

XML

```
<http>
	<!-- ... -->
	<form-login />
</http>
```

Kotlin

```
fun configure(http: HttpSecurity) {
	http {
		// ...
		formLogin { }
	}
}
```

在这种配置 Spring 中，Security 将呈现一个默认的登录页面。大多数生产应用程序将需要一个自定义日志的形式。

下面的配置演示了如何在表单中提供自定义日志。

例 2。自定义登录表单配置

爪哇

```
protected void configure(HttpSecurity http) throws Exception {
	http
		// ...
		.formLogin(form -> form
			.loginPage("/login")
			.permitAll()
		);
}
```

XML

```
<http>
	<!-- ... -->
	<intercept-url pattern="/login" access="permitAll" />
	<form-login login-page="/login" />
</http>
```

Kotlin

```
fun configure(http: HttpSecurity) {
	http {
		// ...
		formLogin {
			loginPage = "/login"
			permitAll()
		}
	}
}
```

当登录页面在 Spring 安全配置中指定时，你负责呈现该页面。下面是一个[Thymeleaf](https://www.thymeleaf.org/)模板，该模板生成符合`/login`登录页面的 HTML 登录表单：

例 3。登录表单

SRC/main/resources/templates/login.html

```
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
	<head>
		<title>Please Log In</title>
	</head>
	<body>
		<h1>Please Log In</h1>
		<div th:if="${param.error}">
			Invalid username and password.</div>
		<div th:if="${param.logout}">
			You have been logged out.</div>
		<form th:action="@{/login}" method="post">
			<div>
			<input type="text" name="username" placeholder="Username"/>
			</div>
			<div>
			<input type="password" name="password" placeholder="Password"/>
			</div>
			<input type="submit" value="Log in" />
		</form>
	</body>
</html>
```

关于默认的 HTML 表单，有几个关键点：

* 表单应该执行`post`到`/login`

* 表单将需要包含一个[CSRF Token](../../exploits/csrf.html#servlet-csrf)，由 ThymeLeaf 表示为[自动包含](../../exploits/csrf.html#servlet-csrf-include-form-auto)。

* 表单应该在名为`username`的参数中指定用户名。

* 表单应该在名为`password`的参数中指定密码。

* 如果发现 HTTP 参数错误，则表示用户未能提供有效的用户名/密码。

* 如果找到了 HTTP 参数注销，则表示用户已成功注销。

许多用户只需要定制登录页面就可以了。然而，如果需要的话，上面的所有内容都可以通过额外的配置进行定制。

如果你正在使用 Spring MVC，你将需要一个控制器，该控制器将`GET /login`映射到我们创建的登录模板。下面可以看到最小样本`LoginController`:

例 4。登录控制器

Java

```
@Controller
class LoginController {
	@GetMapping("/login")
	String login() {
		return "login";
	}
}
```

Kotlin

```
@Controller
class LoginController {
    @GetMapping("/login")
    fun login(): String {
        return "login"
    }
}
```

[读取用户名/密码](input.html)[Basic](basic.html)

