Servlet API 整合

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

本节描述了Spring Security是如何与Servlet API整合的。

Servlet 2.5+ 整合

本节介绍了Spring Security如何与Servlet 2.5规范整合。

HttpServletRequest.getRemoteUser()

HttpServletRequest.getRemoteUser() 返回 SecurityContextHolder.getContext().getAuthentication().getName() 的结果,这通常是当前的用户名。如果你想在你的应用程序中显示当前的用户名,这可能是有用的。此外,你可以检查这个是否为空,以确定一个用户是否已经认证或匿名。知道用户是否通过认证对于确定是否应该显示某些UI元素很有用(例如,只有在用户通过认证的情况下才应该显示注销登录链接)。

HttpServletRequest.getUserPrincipal()

HttpServletRequest.getUserPrincipal()返回 SecurityContextHolder.getContext().getAuthentication() 的结果。这意味着它是一个 Authentication,当使用基于用户名和密码的认证时,它通常是 UsernamePasswordAuthenticationToken 的一个实例。如果你需要关于你的用户的额外信息,这可能是有用的。例如,你可能已经创建了一个自定义的 UserDetailsService,它返回一个自定义的 UserDetails,包含你的用户的名字和姓氏。你可以通过以下方式获得这些信息。

  • Java

  • Kotlin

Authentication auth = httpServletRequest.getUserPrincipal();
//假设整合的自定义UserDetails被称为MyCustomUserDetails
//默认情况下,通常是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的任何耦合。

HttpServletRequest.isUserInRole(String)

HttpServletRequest.isUserInRole(String) 确定 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 是否包含一个具有传入 isUserInRole(String) 的角色的 GrantedAuthority。通常情况下,用户不应该向这个方法传递 ROLE_ 前缀,因为它是自动添加的。例如,如果你想确定当前用户是否有 "ROLE_ADMIN" 的权限,你可以使用以下方法。

  • Java

  • Kotlin

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

这对于确定是否应该显示某些用户界面组件可能是有用的。例如,你可能只在当前用户是管理员的情况下显示管理员链接。

Servlet 3+ 整合

下一节介绍了Spring Security与之整合的Servlet 3方法。

HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)

你可以使用 HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse) 方法来确保用户被认证。如果他们没有通过认证,配置的 AuthenticationEntryPoint 将被用来请求用户进行认证(重定向到登录页面)。

HttpServletRequest.login(String,String)

你可以使用 HttpServletRequest.login(String,String) 方法,用当前的 AuthenticationManager 来验证用户。例如,下面将尝试用用户的 usernamepassword 的密码进行认证。

  • Java

  • Kotlin

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

如果你想让Spring Security处理失败的认证尝试,你不需要catch ServletException

HttpServletRequest.logout()

你可以使用 HttpServletRequest.logout() 方法来注销当前用户。

通常情况下,这意味着 SecurityContextHolder 被清空,HttpSession 被失效,任何 "记住我"(Remember Me) 的认证被清理,等等。然而,配置的 LogoutHandler 实现是不同的,这取决于你的Spring Security 配置。注意,在 HttpServletRequest.logout() 被调用后,你仍然负责写出一个响应。通常情况是把用户重定向到欢迎页面。

AsyncContext.start(Runnable)

AsyncContext.start(Runnable) 方法确保你的凭证被传播到新的 Thread。通过使用Spring Security的并发支持,Spring Security覆盖了 AsyncContext.start(Runnable) 以确保在处理 Runnable 时使用当前的 SecurityContext。下面的例子输出了当前用户的 Authentication

  • Java

  • Kotlin

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。

<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 被设置为处理异步请求。

<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确保你的 SecurityContext 也能在异步请求中得到传播。

那么它是如何工作的呢?如果你不太感兴趣,可以跳过本节的剩余部分。 这方面的大部分内容都是Servlet规范中的内容,但Spring Security做了一点调整,以确保异步请求的正常工作。在Spring Security 3.2之前,一旦 HttpServletResponse 被提交(响应),来自 SecurityContextHolderSecurityContext 就会自动保存。这在异步环境中会导致问题。请看下面的例子。

  • Java

  • Kotlin

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 在提交 HttpServletResponse 时自动保存了 SecurityContext,它就会失去一个登录的用户。

从3.2版本开始,Spring Security 足够聪明,只要调用 HttpServletRequest.startAsync(),就不再自动在提交 HttpServletResponse 时保存 SecurityContext

Servlet 3.1+ 整合

以下部分描述了Spring Security与之整合的Servlet 3.1方法。

HttpServletRequest#changeSessionId()

HttpServletRequest.changeSessionId() 是Servlet 3.1及以上版本中保护 Session Fixation(会话固定) 攻击的默认方法。