Spring Security 从根本上讲是线程绑定的,因为它需要使当前经过身份验证的主体可供各种下游使用者使用。基本构件是 SecurityContext
,它可以包含一个 Authentication
(当用户登录时,它将是一个经过显式 authenticated
的 Authentication
)。你始终可以通过 SecurityContextHolder
中的静态便捷方法来访问和操作 SeucurityContext
,而该方法又可以简单地操作 ThreadLocal
,例如:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);
用户应用代码执行该操作并不常见,但是例如在你需要编写自定义身份验证过滤器时可能会很有用(尽管即使如此,Spring Security 中也可以使用基类来避免需要使用 SecurityContextHolder
)。
如果你需要访问 Web 端点中当前经过身份验证的用户,则可以在 @RequestMapping
中使用 method 参数。例如:
@RequestMapping("/foo")
public String foo(@AuthenticationPrincipal User user) {
... // do stuff with user
}
该注解将当前的 Authentication
从 SecurityContext
中拉出,并对其调用 getPrincipal()
方法以产生 method 参数。Authentication
中的 Principal
类型取决于验证身份验证的 AuthenticationManager
,因此这是获得对用户数据的类型安全应用的有用的小技巧。
如果使用 Spring Security,则 HttpServletRequest
中的 Principal
将为 Authentication
类型,因此你也可以直接使用它:
@RequestMapping("/foo")
public String foo(Principal principal) {
Authentication authentication = (Authentication) principal;
User = (User) authentication.getPrincipal();
... // do stuff with user
}
如果你需要编写在不使用 Spring Security 时可以工作的代码,那么有时这很有用(你需要在装入 Authentication
类时更具防御性)。
异步处理安全方法
由于 SecurityContext
是线程绑定的,因此,如果要执行任何调用安全方法的后台处理,例如使用 @Async
,你需要确保传播上下文。这归结为将 SecurityContext
打包为在后台执行的任务(Runnable
、Callable
等)。Spring Security 提供了一些帮助程序,例如 Runnable
和 Callable
的包装器。要将 SecurityContext
传播到 @Async
方法,你需要提供 AsyncConfigurer
并确保 Executor
具有正确的类型:
@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
}
}