表单登录
Spring Security 提供了通过 HTML 表单提供用户名和密码的支持。本节详细介绍了基于表单的身份验证在 Spring Security 中的工作原理。
本节将探讨基于表单的登录在Spring Security中的工作原理。首先,我们来看看用户是如何被重定向到登录表单的:
图 1. 重定向到登录页面
上图基于我们的SecurityFilterChain图构建。
1 首先,用户向其无权访问的资源(/private
)发出未经身份验证的请求。
2 Spring Security 的 AuthorizationFilter 通过抛出 AccessDeniedException
异常来表示未认证的请求被拒绝。
3 由于用户未通过身份验证,ExceptionTranslationFilter 将启动 Start Authentication 并重定向到带有配置的 AuthenticationEntryPoint 的登录页面。在大多数情况下,AuthenticationEntryPoint
是 LoginUrlAuthenticationEntryPoint 的实例。
4 浏览器请求它被重定向到的登录页面。
5 应用程序中的某个部分必须渲染登录页面。
当用户名和密码被提交时,UsernamePasswordAuthenticationFilter
会从 HttpServletRequest
实例中提取用户名和密码,并创建一个 UsernamePasswordAuthenticationToken
,这是一种 Authentication。UsernamePasswordAuthenticationFilter
继承自 AbstractAuthenticationProcessingFilter,因此以下图表看起来应该非常相似:
图 2. 验证用户名和密码
该图基于我们的SecurityFilterChain 图表构建。
1 当用户提交其用户名和密码时,UsernamePasswordAuthenticationFilter
会从 HttpServletRequest
实例中提取用户名和密码,并创建一个 UsernamePasswordAuthenticationToken
,这是一种 Authentication。
2 接下来,UsernamePasswordAuthenticationToken
被传递给 AuthenticationManager
实例进行认证。AuthenticationManager
的具体细节取决于用户信息的存储方式。
3 如果身份验证失败,则为 Failure。
-
调用
RememberMeServices.loginFail
。如果未配置记住我功能,这将是一个无操作。请参阅 Javadoc 中的 RememberMeServices 接口。 -
调用
AuthenticationFailureHandler
。请参阅 Javadoc 中的 AuthenticationFailureHandler 类。
4 如果身份验证成功,则为 Success。
-
SessionAuthenticationStrategy
被通知新的登录。参见 Javadoc 中的 SessionAuthenticationStrategy 接口。 -
Authentication 被设置在 SecurityContextHolder 上。参见 Javadoc 中的 SecurityContextPersistenceFilter 类。
-
RememberMeServices.loginSuccess
被调用。如果未配置记住我功能,这将是一个空操作。参见 Javadoc 中的 RememberMeServices 接口。 -
ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
事件。 -
AuthenticationSuccessHandler
被调用。通常,这是一个SimpleUrlAuthenticationSuccessHandler
,它会重定向到我们在重定向到登录页面时由 ExceptionTranslationFilter 保存的请求。
默认情况下,Spring Security 表单登录是启用的。但是,一旦提供了任何基于 servlet 的配置,就必须显式提供基于表单的登录。以下示例显示了最小化的显式 Java 配置:
- Java
- XML
- Kotlin
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(withDefaults());
// ...
}
<http>
<!-- ... -->
<form-login />
</http>
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
formLogin { }
}
// ...
}
在上述配置中,Spring Security 会渲染一个默认的登录页面。大多数生产应用程序需要一个自定义的登录表单。
以下配置演示了如何提供自定义登录表单。
- Java
- XML
- Kotlin
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
// ...
}
<http>
<!-- ... -->
<intercept-url pattern="/login" access="permitAll" />
<form-login login-page="/login" />
</http>
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
formLogin {
loginPage = "/login"
permitAll()
}
}
// ...
}
当在Spring Security配置中指定了登录页面时,你需要负责渲染该页面。下面的 Thymeleaf 模板生成一个符合 /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 表单,有几点需要注意:
许多用户只需要自定义登录页面。然而,如果需要的话,您可以通过额外的配置来自定义前面展示的所有内容。
如果你使用 Spring MVC,你需要一个控制器将 GET /login
映射到我们创建的登录模板。以下示例展示了一个最小化的 LoginController
:
- Java
- Kotlin
@Controller
class LoginController {
@GetMapping("/login")
String login() {
return "login";
}
}
@Controller
class LoginController {
@GetMapping("/login")
fun login(): String {
return "login"
}
}