表单登录(Form Login)

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

Spring Security提供了对通过HTML表单提供用户名和密码的支持。本节将详细介绍基于表单的认证在Spring Security中如何工作。

本节研究了基于表单的登录在Spring Security中是如何工作的。首先,我们看到用户是如何被重定向到登录表单的。

loginurlauthenticationentrypoint
Figure 1. Redirecting to the Login Page

上图建立在 SecurityFilterChain 图上。

number 1 首先,一个用户向其未被授权的资源(/private)发出一个未经认证的请求。

number 2 pring Security 的 AuthorizationFilter 通过抛出一个 AccessDeniedException 来表明未经认证的请求被拒绝了。

number 3 由于用户没有被认证,ExceptionTranslationFilter 启动了 Start Authentication,并发送一个重定向到配置的 AuthenticationEntryPoint 的登录页面。在大多数情况下, AuthenticationEntryPointLoginUrlAuthenticationEntryPoint 的一个实例。

number 4 浏览器请求进入其被重定向的登录页面。

number 5 应用程序中的某些东西,必须渲染登录页面

当用户名和密码被提交后,UsernamePasswordAuthenticationFilter 会对用户名和密码进行认证。UsernamePasswordAuthenticationFilter 扩展了 AbstractAuthenticationProcessingFilter,所以下面的图看起来应该很相似。

usernamepasswordauthenticationfilter
Figure 2. Authenticating Username and Password

上图建立在 SecurityFilterChain 图上。

number 1 当用户提交他们的用户名和密码时,UsernamePasswordAuthenticationFilter 通过从 HttpServletRequest 实例中提取用户名和密码,创建一个 UsernamePasswordAuthenticationToken,这是一种 Authentication 类型。

number 2 接下来,UsernamePasswordAuthenticationToken 被传入 AuthenticationManager 实例,以进行认证。AuthenticationManager 的细节取决于 用户信息的存储方式

number 3 如果认证失败,则为 Failure.

  1. SecurityContextHolder 被清空。

  2. RememberMeServices.loginFail 被调用。如果没有配置remember me,这就是一个无用功。参见Javadoc中的 RememberMeServices 接口。

  3. AuthenticationFailureHandler 被调用。参见Javadoc中的 AuthenticationFailureHandler 类。

number 4 如果认证成功,则 Success

  1. SessionAuthenticationStrategy 被通知有新的登录。参见Javadoc中的 SessionAuthenticationStrategy 接口。

  2. Authentication 被设置在 SecurityContextHolder 上。参见 Javadoc 中的 SecurityContextPersistenceFilter 类。

  3. RememberMeServices.loginSuccess 被调用。如果没有配置remember me,这就是一个无用功。参见Javadoc中的 RememberMeServices 接口。

  4. ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent 事件。

  5. AuthenticationSuccessHandler 被调用。通常,这是一个 SimpleUrlAuthenticationSuccessHandler,当我们重定向到登录页面时,它会重定向到由 ExceptionTranslationFilter 保存的请求。

默认情况下,Spring Security表单登录被启用。然而,只要提供任何基于Servlet的配置,就必须明确提供基于表单的登录。下面的例子显示了一个最小的、明确的Java配置。

Form Login
  • 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渲染了一个默认的登录页面。大多数生产应用需要一个自定义的登录表单。

下面的配置演示了如何提供一个自定义的登录表单。

Example 1. Custom Login Form Configuration
Java
public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		.formLogin(form -> form
			.loginPage("/login")
			.permitAll()
		);
	// ...
}
XML
<http>
	<!-- ... -->
	<intercept-url pattern="/login" access="permitAll" />
	<form-login login-page="/login" />
</http>
Kotlin
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
	http {
		formLogin {
			loginPage = "/login"
			permitAll()
		}
	}
	// ...
}

当登录页面在Spring Security配置中被指定时,你要负责渲染该页面。 下面的 Thymeleaf 模板产生一个符合 /login 的登录页面的HTML登录表单。

Login Form - 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,Thymeleaf 会 自动包含

  • 该表单应在一个名为 username 的参数中指定用户名。

  • 表单应该在一个名为 password 的参数中指定密码。

  • 如果发现名为 error 的HTTP参数,表明用户未能提供一个有效的用户名或密码。

  • 如果发现名为 logout 的HTTP参数,表明用户已经成功注销。

许多用户除了定制登录页面外,并不需要更多的东西。然而,如果需要的话,你可以通过额外的配置来定制前面显示的一切。

如果你使用Spring MVC,你需要一个控制器,将 GET /login 映射到我们创建的登录模板。下面的例子展示了一个最小的 LoginController

LoginController
  • Java

  • Kotlin

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