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();
// 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 之间的任何耦合。 |
HttpServletRequest.isUserInRole(String)
HttpServletRequest.isUserInRole(String)
确定 SecurityContextHolder.getContext().getAuthentication().getAuthorities()
是否包含一个 GrantedAuthority
,该 GrantedAuthority
的角色传递到 isUserInRole(String)
中。通常,用户不应将 ROLE_
前缀传递给此方法,因为它会自动添加。例如,如果您想确定当前用户是否具有“ROLE_ADMIN”权限,您可以使用以下方法
-
Java
-
Kotlin
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")
这可能有助于确定是否应显示某些 UI 组件。例如,您可能仅在当前用户是管理员时显示管理员链接。
Servlet 3+ 集成
以下部分描述了 Spring Security 集成的 Servlet 3 方法。
HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
您可以使用 HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)
方法确保用户已进行身份验证。如果他们没有进行身份验证,则使用配置的 AuthenticationEntryPoint
请求用户进行身份验证(重定向到登录页面)。
HttpServletRequest.login(String,String)
您可以使用 HttpServletRequest.login(String,String)
方法使用当前 AuthenticationManager
对用户进行身份验证。例如,以下操作将尝试使用用户名 user
和密码 password
进行身份验证
-
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 处理失败的身份验证尝试,则无需捕获 |
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
-
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 架构
<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 之前,SecurityContextHolder
中的 SecurityContext
会在 HttpServletResponse
提交后立即自动保存。这可能会在异步环境中造成问题。请考虑以下示例
-
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()
问题在于 Spring Security 不认识此 Thread
,因此 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 及更高版本中防止 会话固定 攻击的默认方法。