我有一个测试应用程序使用Spring安全性,有3个用户[suzy,frank,julie]。我的应用程序将事件发送到外部安全分析系统。该系统分析事件以确定它是否是一种攻击"然后应用程序轮询该系统以查看是否有任何事件被认为需要响应。这是一个例子:
一些注意事项:
我基本上想要采取任何行动来对bob的下一个请求产生影响。我想要像:
loadUserByUsername("bob").disable();
我现在想要构建的功能只是:
我可以通过缓存用户会话然后使其无效来注销,但我更喜欢更好的方式(即loadByUsername("bob").logout()
)。我无法弄清楚如何执行帐户停用。
我希望如果可能的话,不必构建核心apis的自定义实现。我希望任何使用Spring security的人都可以轻松集成此功能。
答案 0 :(得分:4)
我会将其分解为两个问题:
不幸的是,如果不以某种方式扩展核心API,我认为没有办法做到这一点。幸运的是,与核心API集成非常简单。
我为此概述了一些方法,因为我不确定“背景方法”是否提供了很多优势(稍后会详细介绍)。
目前,我认为这是最好的方法。
后台线程方法(根据请求)是有意义的,因此您在向应用程序发出请求时不必阻止。但是,在没有某些共享数据存储的情况下,您无法轻松地与另一个线程(更不用说在集群环境实例中的另一台计算机上的另一个进程)进行通信。
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
实现。无论哪种方式,都有对商店的阻止调用。
注意:我不认为这是正确的方法,因此这个答案并不详细。
第一步是编写轮询外部系统的后台任务。显然,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,我们就可以提出这样的请求:
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
1}}为isAccountNonLocked
返回false。