跳到主要内容

Servlet API 集成

QWen Max 中英对照 Servlet APIs Servlet API integration

Servlet 2.5+ 集成

本节描述了 Spring Security 如何与 Servlet 2.5 规范集成。

HttpServletRequest.getRemoteUser()

HttpServletRequest.getRemoteUser() 返回 SecurityContextHolder.getContext().getAuthentication().getName() 的结果,这通常是当前用户名。如果你希望在应用程序中显示当前用户名,这会很有用。此外,你可以检查其是否为 null 以确定用户是否已认证或为匿名。知道用户是否已认证对于决定某些 UI 元素是否应该显示非常有用(例如,仅在用户已认证时才显示的注销链接)。

HttpServletRequest.getUserPrincipal()

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

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();
java
备注

需要注意的是,在整个应用程序中执行如此多的逻辑通常是不好的做法。相反,应该将其集中起来,以减少 Spring Security 和 Servlet API 之间的任何耦合。

HttpServletRequest.isUserInRole(String)

HttpServletRequest.isUserInRole(String) 用于确定 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 是否包含传递给 isUserInRole(String) 的角色的 GrantedAuthority。通常,用户不应该将 ROLE_ 前缀传递给此方法,因为该前缀会自动添加。例如,如果你想确定当前用户是否有 "ROLE_ADMIN" 权限,你可以使用以下代码:

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

这可能有助于确定是否应显示某些 UI 组件。例如,你可能只在当前用户是管理员时才显示管理员链接。

Servlet 3+ 集成

以下章节描述了Spring Security所集成的Servlet 3方法。

HttpServletRequest.authenticate(HttpServletResponse)

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

HttpServletRequest.login(String,String)

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

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

如果您希望 Spring Security 处理失败的认证尝试,则不需要捕获 ServletException

HttpServletRequest.logout()

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

通常,这意味着 SecurityContextHolder 会被清空,HttpSession 会被失效,任何“记住我”认证会被清理,等等。但是,配置的 LogoutHandler 实现会根据你的 Spring Security 配置有所不同。请注意,在调用 HttpServletRequest.logout() 之后,你仍然需要负责写入响应。通常,这将涉及重定向到欢迎页面。

AsyncContext.start(Runnable)

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

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

异步 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>
xml

接下来,你需要确保你的 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>
xml

现在 Spring Security 确保你的 SecurityContext 也会在异步请求中传播。

那么它是如何工作的呢?如果你对此不感兴趣,可以跳过本节的剩余部分。大部分内容都内置于 Servlet 规范中,但 Spring Security 进行了一些微调,以确保异步请求能够正常工作。在 Spring Security 3.2 之前,SecurityContext 会在 HttpServletResponse 被提交时自动从 SecurityContextHolder 中保存。这在异步环境中可能会导致问题。请考虑以下示例:

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

问题是这个 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 及更高版本中用于防范 会话固定 攻击的默认方法。