Servlet API 集成

Servlet 2.5+ 集成

本节介绍 Spring Security 如何与 Servlet 2.5 规范进行集成。spring-doc.cadn.net.cn

HttpServletRequest.getRemoteUser()

HttpServletRequest.getRemoteUser() 返回 SecurityContextHolder.getContext().getAuthentication().getName() 的结果,通常是当前用户名。如果您希望在应用程序中显示当前用户名,这非常有用。 此外,您可以检查其是否为 null,以判断用户是否已认证或为匿名用户。 了解用户是否已认证对于确定是否应显示某些 UI 元素非常有用(例如,仅当用户已认证时才应显示的注销链接)。spring-doc.cadn.net.cn

HttpServletRequest.getUserPrincipal()

HttpServletRequest.getUserPrincipal() 返回 SecurityContextHolder.getContext().getAuthentication() 的结果。 这意味着它是一个 Authentication,在使用基于用户名和密码的认证时,它通常是 UsernamePasswordAuthenticationToken 的实例。 如果您需要有关用户的额外信息,这可能非常有用。 例如,您可能创建了一个自定义的 UserDetailsService,它返回一个包含用户姓名的自定义 UserDetails。 您可以通过以下方式获取此信息:spring-doc.cadn.net.cn

Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
val auth: Authentication = httpServletRequest.getUserPrincipal()
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
val firstName: String = userDetails.firstName
val lastName: String = userDetails.lastName

需要注意的是,在整个应用程序中执行如此多的逻辑通常是一种不良实践。 相反,应当将其集中处理,以减少 Spring Security 与 Servlet API 之间的耦合。spring-doc.cadn.net.cn

HttpServletRequest.isUserInRole(String)

HttpServletRequest.isUserInRole(String) 用于判断 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 是否包含具有通过 isUserInRole(String) 传入的角色的 GrantedAuthority。 通常,用户不应将此方法的 ROLE_ 前缀作为参数传入,因为该前缀会自动添加。 例如,如果您想确定当前用户是否拥有“ROLE_ADMIN”权限,可以使用以下代码:spring-doc.cadn.net.cn

boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")

这可能有助于判断是否应显示某些 UI 组件。 例如,仅当当前用户是管理员时,才显示管理链接。spring-doc.cadn.net.cn

Servlet 3+ 集成

以下部分描述了 Spring Security 所集成的 Servlet 3 方法。spring-doc.cadn.net.cn

HttpServletRequest.authenticate(HttpServletResponse)

您可以使用 HttpServletRequest.authenticate(HttpServletResponse) 方法来确保用户已通过身份验证。 如果用户未通过身份验证,将使用配置的 AuthenticationEntryPoint 来请求用户进行身份验证(重定向到登录页面)。spring-doc.cadn.net.cn

HttpServletRequest.login(String,String)

您可以使用 HttpServletRequest.login(String,String) 方法来使用当前的 AuthenticationManager 对用户进行身份验证。 例如,以下代码将尝试使用用户名 user 和密码 password 进行身份验证:spring-doc.cadn.net.cn

try {
httpServletRequest.login("user","password");
} catch(ServletException ex) {
// fail to authenticate
}
try {
    httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
    // fail to authenticate
}

如果你希望 Spring Security 处理认证失败的尝试,则无需捕获 ServletExceptionspring-doc.cadn.net.cn

HttpServletRequest.logout()

您可以使用 logout 方法注销当前用户。spring-doc.cadn.net.cn

通常,这意味着会清除 SecurityContextHolder、使 HttpSession 失效、清理所有“记住我”(Remember Me)身份验证等。 然而,所配置的 LogoutHandler 实现会根据您的 Spring Security 配置而有所不同。 请注意,在调用 HttpServletRequest.logout() 之后,您仍需负责写出响应。 通常,这包括重定向到欢迎页面。spring-doc.cadn.net.cn

AsyncContext.start(Runnable)

AsyncContext.start(Runnable) 方法确保您的凭据被传播到新的 Thread。 通过使用 Spring Security 的并发支持,Spring Security 会覆盖 AsyncContext.start(Runnable),以确保在处理 Runnable 时使用当前的 SecurityContext。 以下示例输出当前用户的 Authentication:spring-doc.cadn.net.cn

final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
	public void run() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		try {
			final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
			asyncResponse.setStatus(HttpServletResponse.SC_OK);
			asyncResponse.getWriter().write(String.valueOf(authentication));
			async.complete();
		} catch(Exception ex) {
			throw new RuntimeException(ex);
		}
	}
});
val async: AsyncContext = httpServletRequest.startAsync()
async.start {
    val authentication: Authentication = SecurityContextHolder.getContext().authentication
    try {
        val asyncResponse = async.response as HttpServletResponse
        asyncResponse.status = HttpServletResponse.SC_OK
        asyncResponse.writer.write(String.valueOf(authentication))
        async.complete()
    } catch (ex: Exception) {
        throw RuntimeException(ex)
    }
}

异步 Servlet 支持

如果你使用基于 Java 的配置,那么你已经可以开始使用了。 如果你使用 XML 配置,则需要进行一些更新。 第一步是确保你已将 web.xml 文件更新为至少使用 3.0 版本的 schema:spring-doc.cadn.net.cn

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

</web-app>

接下来,你需要确保你的 springSecurityFilterChain 已配置为处理异步请求:spring-doc.cadn.net.cn

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
	org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>

现在,Spring Security 还确保在异步请求中传播您的 SecurityContextspring-doc.cadn.net.cn

那么它是如何工作的呢?如果您对此不太感兴趣,可以随时跳过本节的其余部分。 大部分功能已内置于 Servlet 规范中,但 Spring Security 还做了一些微调,以确保在异步请求环境下能够正常工作。 在 Spring Security 3.2 之前,SecurityContext 中的 SecurityContextHolder 会在 HttpServletResponse 一经提交时自动保存。 这在异步环境中可能会引发问题。 请考虑以下示例:spring-doc.cadn.net.cn

httpServletRequest.startAsync();
new Thread("AsyncThread") {
	@Override
	public void run() {
		try {
			// Do work
			TimeUnit.SECONDS.sleep(1);

			// Write to and commit the httpServletResponse
			httpServletResponse.getOutputStream().flush();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}.start();
httpServletRequest.startAsync()
object : Thread("AsyncThread") {
    override fun run() {
        try {
            // Do work
            TimeUnit.SECONDS.sleep(1)

            // Write to and commit the httpServletResponse
            httpServletResponse.outputStream.flush()
        } catch (ex: java.lang.Exception) {
            ex.printStackTrace()
        }
    }
}.start()

问题是这个 Thread 不被 Spring Security 所知,因此 SecurityContext 不会传播到该线程中。 这意味着,当我们提交 HttpServletResponse 时,不存在 SecurityContext。 当 Spring Security 在提交 SecurityContext 时自动保存 HttpServletResponse,就会丢失已登录的用户信息。spring-doc.cadn.net.cn

从 3.2 版本起,Spring Security 已足够智能,不再在调用 SecurityContext 后,于提交 HttpServletResponse 时自动保存 HttpServletRequest.startAsync()spring-doc.cadn.net.cn

Servlet 3.1+ 集成

以下部分描述了 Spring Security 所集成的 Servlet 3.1 方法。spring-doc.cadn.net.cn

HttpServletRequest#changeSessionId()

HttpServletRequest.changeSessionId() 是 Servlet 3.1 及更高版本中用于防范 会话固定(Session Fixation) 攻击的默认方法。spring-doc.cadn.net.cn