我将把它分解为你的两个问题:
不幸的是,如果不以某种方式扩展核心API,我认为没有办法做到这一点。幸运的是,与核心API集成相当简单。
我正在概述一些方法,因为我不确定“背景方法”是否提供了很多优势(稍后将详细介绍)。
在这个时候,我认为这是最好的方法。
后台线程方法(根据请求)是有意义的,因此您在向应用程序发出请求时不必阻止。但是,如果没有某些共享数据存储,您将无法轻松地与另一个线程(更不用说在群集环境实例中的另一台计算机上的另一个进程)进行通信。
Spring Security获取当前用户的方式是使用 SecurityContextRepository 实现。默认实现从中获取用户 HttpSession 。你能做的是这样的:
SecurityContextRepository
HttpSession
public class SecurityAnalyzerSecurityContextRepository implements SecurityContextRepository { private final SecurityAnalyzer securityAnalayzer; private final SecurityContextRepository delegate; public SecurityAnalyzerSecurityContextRepository(SecurityAnalyzer securityAnalyzer) { this(securityAnalyzer, new HttpSessionSecurityContextRepository()); } public SecurityAnalyzerSecurityContextRepository(SecurityAnalyzer securityAnalayzer, SecurityContextRepository delegate) { this.securityAnalayzer = securityAnalyzer; this.delegate = delegate; } public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { SecurityContext context = delegate.loadContext(requestResponseHolder); Authentication authentication = context.getAuthentication(); if(authentication == null) { return context; } String principal = authentication.getName(); // your SecurityAnalyzer implementation would need implement isEvil if(securityAnalyzer.isEvil(principal)) { return SecurityContextHolder.createEmptyContext(); } return context; } public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { delegate.saveContext(context, request, response); } public boolean containsContext(HttpServletRequest request) { return delegate.containsContext(request); } }
这个想法是你可以委托给现有的 SecurityContextRepository 并验证用户尚未被确定为邪恶。
或者,你可以提供一个 SecurityContextRepository 从后台线程可以写入的存储加载的实现。无论哪种方式,都有阻止调用商店。
的 注意 强> :我不认为这是正确的方法,因此这个答案并不详细。
第一步是编写轮询外部系统的后台任务。显然,Spring Security无法提供此步骤,因为它不知道如何与外部系统进行交互。
这个代码可能类似于:
SecurityAnalyzer analyzer = ... Set<String> evilUsernames = analyzer.getAndRemoveNewEvilUsernames(); ... what to do with evilUsernames? ...
现在的问题是如何处理evilUsernames。默认情况下,Spring Security将从中获取当前用户 HttpSession 。大多数Servlet容器都默认使用 HttpSession 实现持久到内存 Map 。 Spring Security无法获得对此的引用 Map 。
Map
一种选择是,如果我们能够为每个用户获取JSESSIONID,我们可以像这样提出请求:
GET /j_spring_security_logout HTTP/1.1 Host: www.example.org Cookie: JSESSIONID=<some-id>;
如果启用CSRF保护,这会变得有点困难。为了让它工作,你需要暴露 CsrfToken 作为端点并进行调用以获取它:
CsrfToken
GET /csrf HTTP/1.1 Host: www.example.org Cookie: JSESSIONID=<some-id>;
然后使用响应,您可以使用令牌进行POST:
POST /logout HTTP/1.1 Host: www.example.org Cookie: JSESSIONID=<some-id>; _csrf=<csrf-token>
此时您可能会问,但我怎么知道JSESSIONID?一种选择是使用Spring Security 并发控制 。但是,开箱即用的实现不适用于集群实现。
最终,需要将信息传递到SecurityAnalyzer并从SecurityAnalyzer返回,否则将需要自定义Spring Security API。
一旦Spring Session增加支持 按用户标识查询会话 ,您将能够通过用户名使会话无效。
或者,如果SecurityAnalyzer知道会话ID,那么Spring Session现在可以正常工作。
您还需要知道如何锁定帐户。 Spring Security提供了一种支持锁定帐户的机制。
你可以使用 UserDetailsManager 实施更新 UserDetails 为了返回false isAccountNonLocked 。然后是Spring Security的 DaoAuthenticationProvider 将利用 preAuthenticationChecks 这可确保帐户未被锁定。
UserDetailsManager
UserDetails
isAccountNonLocked
DaoAuthenticationProvider
preAuthenticationChecks
如果你自己写的 UserDetailsService ,然后内置 UserDetailsManager 实现将无法工作,因此您需要更新您的用户模型以便自定义 UserDetailsService 创造一个 UserDetailsService 返回false为 isAccountNonLocked 。
UserDetailsService