Spring Security:按名称注销,锁定或禁用用户

时间:2015-04-19 04:34:00

标签: spring-security

我有一个测试应用程序使用Spring安全性,有3个用户[suzy,frank,julie]。我的应用程序将事件发送到外部安全分析系统。该系统分析事件以确定它是否是一种攻击"然后应用程序轮询该系统以查看是否有任何事件被认为需要响应。这是一个例子:

  1. bob执行动作1(发送事件)
  2. bob执行(坏)2(发送事件)
  3. bob执行动作3(发送事件)
  4. 应用程序轮询外部系统并发现bob现在应该已注销
  5. bob退出(如何?)
  6. bob的下一个请求被重定向到登录页面
  7. 一些注意事项:

    • 外部分析系统正在检查弹簧安全无法检查的事情(想想欺诈分析或类似事情)
    • "民意调查" check本质上是一个后台线程,因此它不会阻止请求

    我基本上想要采取任何行动来对bob的下一个请求产生影响。我想要像:

    loadUserByUsername("bob").disable();
    

    我现在想要构建的功能只是:

    • 注销
    • 禁用

    我可以通过缓存用户会话然后使其无效来注销,但我更喜欢更好的方式(即loadByUsername("bob").logout())。我无法弄清楚如何执行帐户停用。

    我希望如果可能的话,不必构建核心apis的自定义实现。我希望任何使用Spring security的人都可以轻松集成此功能。

1 个答案:

答案 0 :(得分:4)

我会将其分解为两个问题:

以编程方式记录用户

不幸的是,如果不以某种方式扩展核心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实现。无论哪种方式,都有对商店的阻止调用。

背景线程方法

注意:我不认为这是正确的方法,因此这个答案并不详细。

第一步是编写轮询外部系统的后台任务。显然,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 1}}为isAccountNonLocked返回false。