# Servlet API集成

# Servlet 2.5+集成

# HttpServletRequest.getRemoteUser()

[HttpServletRequest.getRemoteUser()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser())将返回SecurityContextHolder.getContext().getAuthentication().getName()的结果,这通常是当前的用户名。如果你希望在应用程序中显示当前用户名,这将非常有用。此外,检查此值是否为null可以用来指示用户是否已通过身份验证或是匿名的。了解用户是否经过了身份验证,对于确定某些UI元素是否应该显示是有用的(例如,只有在用户经过身份验证的情况下才应该显示注销链接)。

# HttpServletRequest.getUserPrincipal()

[HttpServletRequest.getUserPrincipal()](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal())将返回SecurityContextHolder.getContext().getAuthentication()的结果。这意味着它是Authentication,当使用基于用户名和密码的身份验证时,它通常是UsernamePasswordAuthenticationToken的一个实例。如果你需要有关你的用户的其他信息,这可能是有用的。例如,你可能已经创建了一个自定义UserDetailsService,它返回一个自定义的UserDetails,其中包含用户的姓和名。你可以通过以下方式获得此信息:

Java

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();

Kotlin

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 安全性和 Servlet API的任何耦合。

# HttpServletRequest.isUserinRole(字符串)

[HttpServletRequest.isUserinRole(字符串)](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#isUserInRole(java.lang.String))将确定SecurityContextHolder.getContext().getAuthentication().getAuthorities()是否包含带有传递到isUserInRole(String)的角色的GrantedAuthority。通常,用户不应该将“role_”前缀传入此方法,因为它是自动添加的。例如,如果你想确定当前用户是否拥有“role_admin”权限,可以使用以下方法:

Java

boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");

Kotlin

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

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

# Servlet 3+集成

下面的部分描述了 Spring 安全性集成的 Servlet 3种方法。

# HttpServletRequest.Authenticate(HttpServletRequest,HttpServletResponse)

可以使用HttpServletRequest.Authenticate(HttpServletRequest,HttpServletResponse) (opens new window)方法来确保对用户进行身份验证。如果未对它们进行身份验证,则将使用配置的身份验证中心点来请求用户进行身份验证(即重定向到登录页面)。

# HttpServletRequest.login(字符串,字符串)

可以使用HttpServletRequest.login(字符串,字符串) (opens new window)方法对当前AuthenticationManager的用户进行身份验证。例如,下面将尝试使用用户名“user”和密码“password”进行身份验证:

Java

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

Kotlin

try {
    httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
    // fail to authenticate
}
如果你希望 Spring 安全性来处理失败的身份验证尝试,那么不需要捕获ServletException。

# HttpServletRequest.logout()

可以使用HttpServletRequest.logout() (opens new window)方法将当前用户注销。

通常,这意味着SecurityContextholder将被清除,HttpSession将无效,任何“记住我”的身份验证都将被清除,等等。然而,所配置的LogouthAndler实现将根据你的 Spring 安全配置而有所不同。需要注意的是,在调用了HttpServletRequest.logout()之后,你仍然负责编写响应。通常情况下,这需要重定向到欢迎页面。

# AsyncContext.start(可运行)

确保你的凭据将被传播到新线程的AsyncContext.start(可运行) (opens new window)方法。利用 Spring Security的并发支持, Spring Security覆盖了AsyncContext.Start,以确保在处理Runnable时使用当前的SecurityContext。例如,下面将输出当前用户的身份验证:

Java

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);
		}
	}
});

Kotlin

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模式,如下所示:

<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 安全性将确保你的SecurityContext也在异步请求上传播。

那么,它是如何工作的呢?如果你真的不感兴趣,请跳过这一节的其余部分,否则请继续阅读。这其中的大部分是内置在 Servlet 规范中的,但是 Spring 安全性做了一些调整,以确保异步请求能够正常工作。在 Spring Security3.2之前,一旦提交了HttpServletResponse,来自SecurityContextholder的SecurityContext就会自动保存。这可能会在异步环境中引起问题。例如,考虑以下几点:

Java

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();

Kotlin

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()

问题是安全性不知道这个线程,因此SecurityContext不会传播到它。这意味着当我们提交HttpServletResponse时,不存在SecurityContext。 Spring 当Security在提交HttpServletResponse时自动保存SecurityContext时,它将丢失已登录的用户。

自版本3.2以来, Spring 安全性已经足够聪明,不再在调用HttpServletRequest.startasync()时提交HttpServletResponse时自动保存SecurityContext。

# Servlet 3.1+集成

下面的部分描述了 Spring 安全性集成的 Servlet 3.1方法。

# HttpServletRequest#changesessionID()

在 Servlet 3.1及更高版本中,[HttpServletRequest.changesessionID()](https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html#changeSessionId())是防止Session Fixation攻击的默认方法。

本地化Spring Data