我将把它分解成你的两个问题:
以编程方式注销用户
不幸的是,我认为没有办法在不以某种方式扩展核心 API 的情况下做到这一点。幸运的是,与核心 API 的集成相当简单。
我为此概述了一些方法,因为我不确定“背景方法”是否提供了很多优势(稍后会详细介绍)。
自定义 SecurityContextRepository
>
此时,我认为这是最好的方法。
后台线程方法(根据要求)是有意义的,因此您在向应用程序发出请求时不必阻塞。但是,如果没有一些共享数据存储,您将无法轻松地与另一个线程(更不用说集群环境实例中另一台计算机上的另一个进程)进行通信。
Spring Security 获取当前用户的方式是使用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 实现,该实现从后台线程可以写入的存储中加载。无论哪种方式,都存在对 store 的阻塞调用。
后台线程方法
注意:我认为这不是正确的方法,因此这个答案没有那么详细。
第一步是编写轮询外部系统的后台任务。显然Spring Security无法提供这一步,因为它不知道如何与外部系统交互。
此代码可能类似于:
SecurityAnalyzer analyzer = ...
Set<String> evilUsernames = analyzer.getAndRemoveNewEvilUsernames();
... what to do with evilUsernames? ...
现在的问题变成了如何处理 evilUsernames。默认情况下,Spring Security 将从HttpSession 获取当前用户。大多数 Servlet 容器默认将 HttpSession 实现持久化到内存中的 Map。 Spring Security 无法获得对此Map 的引用。
直接使用 JSESSIONID
一种选择是,如果我们能够获取每个用户的 JSESSIONID,我们可以发出如下请求:
GET /j_spring_security_logout HTTP/1.1
Host: www.example.org
Cookie: JSESSIONID=<some-id>;
如果启用 CSRF 保护,这将变得更加困难。要使其正常工作,您需要将 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 的concurrency control。但是,开箱即用的实现不适用于集群实现。
最终,需要将信息传递到 SecurityAnalyzer 并从 SecurityAnalyzer 返回,否则将需要自定义 Spring Security API。
春季会议
一旦 Spring Session 添加了对 querying sessions by a user identifier 的支持,您就可以通过用户名使会话无效。
或者,如果 SecurityAnalyzer 知道会话 ID,那么 Spring Session 现在就可以工作了。
锁定帐户
您还需要知道如何锁定帐户。 Spring Security 提供了一种机制来支持锁定帐户。
您可以使用UserDetailsManager 实现来更新UserDetails 以为isAccountNonLocked 返回false。然后 Spring Security 的 DaoAuthenticationProvider 将利用 preAuthenticationChecks 确保帐户不被锁定。
如果您已经编写了自己的UserDetailsService,那么内置的UserDetailsManager 实现将不起作用,因此您需要更新您的用户模型,以便自定义UserDetailsService 创建一个UserDetailsService,该UserDetailsService 为@ 返回false 987654346@.