领先一步
VMware 提供培训和认证,以快速提升您的进度。
了解更多上周我宣布发布 Spring Security 3.2 M1,其中包含改进的 Servlet 3 支持。在这篇文章中,我将介绍 3.2 M1 版本中一些更令人兴奋的功能。具体来说,我们将看看以下新的 Spring Security 功能
你可能会问:“在一个以 Servlet 3 为主题的版本中,并发支持在做什么?”原因是并发支持为该版本中的所有其他功能提供了基础。虽然并发支持被 Servlet 3 集成使用,但它也可以作为构建块来支持任何应用程序中的并发和 Spring Security。现在让我们来看看 Spring Security 的并发支持。
Spring Security 并发支持中最基本的功能块之一是DelegatingSecurityContextRunnable
。它包装一个委托的Runnable
,以便为委托使用指定的SecurityContext
初始化SecurityContextHolder
。然后,它调用委托的Runnable
,并确保之后清除SecurityContextHolder
。DelegatingSecurityContextRunnable
看起来像这样
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
虽然非常简单,但它使从一个Thread
到另一个Thread
传输SecurityContext
变得无缝。这很重要,因为在大多数情况下,SecurityContextHolder
基于每个Thread
。例如,你可能已经使用 Spring Security 的<global-method-security> 支持来保护你的一个服务。你现在可以轻松地将当前Thread
的SecurityContext
传输到调用受保护服务的Thread
。下面是一个你可能如何做到这一点的示例
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
上面的代码执行以下步骤:
Runnable
。请注意,它不知道 Spring Security。SecurityContextHolder
获取我们希望使用的SecurityContext
,并初始化DelegatingSecurityContextRunnable
。DelegatingSecurityContextRunnable
创建一个Thread
。Thread
。由于使用SecurityContextHolder
中的SecurityContext
创建DelegatingSecurityContextRunnable
非常常见,因此有一个快捷构造函数。以下代码与上面的代码相同
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
我们拥有的代码易于使用,但它仍然需要知道我们正在使用 Spring Security。在下一节中,我们将看看如何利用DelegatingSecurityContextExecutor
来隐藏我们正在使用 Spring Security 的事实。
在上一节中,我们发现使用DelegatingSecurityContextRunnable
很容易,但它并不理想,因为我们必须知道 Spring Security 才能使用它。让我们看看DelegatingSecurityContextExecutor
如何保护我们的代码免受任何使用 Spring Security 的知识的影响。
DelegatingSecurityContextExecutor
的设计与DelegatingSecurityContextRunnable
非常相似,只是它接受一个委托的Executor而不是委托的Runnable
。你可以看到它可能如何使用的示例如下
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
context.setAuthentication(authentication);
SimpleAsyncTaskExecutor delegateExecutor =
new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor, context);
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
代码执行以下步骤:
DelegatingSecurityContextExecutor
的SecurityContext
。请注意,在这个例子中,我们只是手动创建SecurityContext
。但是,我们获取SecurityContext
的位置或方式并不重要(即,如果我们想,我们可以从SecurityContextHolder
获取它)。Runnable
。DelegatingSecurityContextExecutor
,它负责使用DelegatingSecurityContextRunnable
包装传递给execute方法的任何Runnable
。然后,它将包装的Runnable
传递给delegateExecutor。在这个例子中,相同的SecurityContext
将用于提交给我们的DelegatingSecurityContextExecutor
的每个Runnable
。如果我们正在运行需要由具有提升权限的用户运行的后台任务,这很好。此时你可能会问自己:“这如何保护我的代码免受任何 Spring Security 知识的影响?”与其在我们自己的代码中创建SecurityContext
和DelegatingSecurityContextExecutor
,我们可以注入一个已初始化的DelegatingSecurityContextExecutor
实例。
@Autowired
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
public void submitRunnable() {
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
}
现在我们的代码不知道SecurityContext
正在传播到Thread
,然后执行originalRunnable,然后清除SecurityContextHolder。在这个例子中,相同的用户被用来执行每个Thread
。如果我们想使用我们在调用executor.execute(Runnable)
时(即当前登录的用户)从SecurityContextHolder
获得的用户来处理originalRunnable怎么办?这可以通过从我们的DelegatingSecurityContextExecutor
构造函数中删除SecurityContext
参数来完成。例如
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
现在,每当执行executor.execute(Runnable)
时,首先通过SecurityContextHolder
获得SecurityContext
,然后使用该SecurityContext
创建我们的DelegatingSecurityContextRunnable
。这意味着我们使用与调用executor.execute(Runnable)
代码相同的用户来执行我们的Runnable
。
请参阅Javadoc,了解与Java并发API和Spring Task抽象的更多集成。一旦你理解了之前的代码,它们就非常容易理解。
Spring Security 很久以前就支持 Servlet API 集成。但是,直到 3.2 M1 才支持 Servlet 3 中添加的新方法。在本节中,我们将讨论 Spring Security 集成的每种方法。如果你想看看它的实际效果,你可以使用 Gradle 插件将 Spring Security 导入 Spring Tool Suite 并运行servletapi 示例应用程序。
Spring Security 现在与HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)集成。简而言之,我们可以使用此方法确保用户已通过身份验证。如果他们没有通过身份验证,则将使用配置的AuthenticationEntryPoint来请求用户进行身份验证(即重定向到登录页面)。
Spring Security 现在与HttpServletRequest.login(String,String)集成。用户可以使用此方法使用 Spring Security 对用户名和密码进行身份验证。如果身份验证失败,将抛出一个包装原始 Spring Security AuthenticationException
的ServletException
。这意味着如果你允许ServletException
传播,Spring Security 的ExceptionTranslationFilter
将为你处理它。或者,你可以捕获ServletException
并自己处理它。
Spring Security 现在通过调用配置的LogoutHandler
实现与HttpServletRequest.logout()集成。通常这意味着SecurityContextHolder
将被清除,HttpSession
将失效,任何“记住我”身份验证都将被清理等。但是,配置的LogoutHandler
实现将根据你的 Spring Security 配置而有所不同。重要的是要注意,在调用HttpServletRequest.logout()
之后,你仍然负责写入响应。这通常涉及重定向到欢迎页面。
AsynchContext.start(Runnable)方法确保你的凭据将传播到新的Thread
。使用 Spring Security 新添加的并发支持,Spring Security 覆盖AsyncContext.start(Runnable)
以确保在处理Runnable
时使用当前SecurityContext
。
Spring Security 现在支持 Servlet 3,异步请求。那么如何使用它呢?
第一步是确保你已更新你的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 http://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
。
那么发生了什么变化呢?Spring Security 的内部重构将确保当另一个Thread
提交响应时,您的SecurityContext
不会被清除,从而避免用户看起来已注销的情况。此外,您可以使用 Spring Security 并发支持和 Spring Security 的AsyncContext.start(Runnable)
集成来帮助您处理 Servlet 请求。
[callout title=将 SecurityContext 与 Callable 关联] 更技术地说,Spring Security 集成到WebAsyncManager。用于处理Callable
的SecurityContext
是在调用startCallableProcessing时SecurityContextHolder
上存在的SecurityContext
。[/callout]
正如Rossen在之前的博文中演示的那样,Spring Web MVC 3.2 有优秀的 Servlet 3 异步支持。无需任何额外配置,Spring Security 将自动将SecurityContext
设置到执行控制器返回的Callable
的Thread
。例如,以下方法将自动在其Callable
上执行,并使用创建Callable
时可用的SecurityContext
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}
控制器返回的DeferredResult
没有自动集成。这是因为DeferredResult
由用户处理,因此无法自动与其集成。但是,您仍然可以使用并发支持来提供与 Spring Security 的透明集成。
我希望这能使您更好地理解 Spring Security 3.2 M1 中提供的更改,并让您对下一个里程碑感到兴奋。作为社区的一员,我鼓励您试用新的里程碑,并在JIRA中报告任何错误/增强功能。此反馈是一种简单但非常重要的回馈社区的方式!