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
来验证用户。例如,下面将尝试用用户的 username
和 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处理失败的认证尝试,你不需要catch |
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
被提交(响应),来自 SecurityContextHolder
的 SecurityContext
就会自动保存。这在异步环境中会导致问题。请看下面的例子。
-
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(会话固定) 攻击的默认方法。