【问题标题】:Spring Security: logout, lock, or disable user by nameSpring Security:按名称注销、锁定或禁用用户
【发布时间】:2015-04-20 23:39:38
【问题描述】:

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

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

一些注意事项:

  • 外部分析系统正在检查 spring security 不会检查的东西(想想欺诈分析或类似的东西)
  • “轮询”检查本质上是一个后台线程,因此它不会阻塞请求

我基本上希望我采取的任何行动都能对鲍勃的下一个请求产生影响。我想要类似的东西:

loadUserByUsername("bob").disable();

我现在希望构建的功能只是:

  • 注销
  • 禁用

我可以通过缓存用户会话然后使其无效来注销,尽管我更喜欢更好的方法(即loadByUsername("bob").logout())。我不知道如何执行帐户禁用。

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

【问题讨论】:

    标签: spring-security


    【解决方案1】:

    我将把它分解成你的两个问题:

    以编程方式注销用户

    不幸的是,我认为没有办法在不以某种方式扩展核心 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@.

    【讨论】:

    • 正如承诺的那样,想提供更新。这非常有效。在配置HttpSecurity 对象时,我必须添加的唯一部分是.and().securityContext() .securityContextRepository(myCustomSecurityContextRepository).and()。感谢您提供非常有帮助的回复。
    猜你喜欢
    • 2017-03-22
    • 2016-07-21
    • 2012-11-10
    • 2014-12-11
    • 2014-04-16
    • 2017-05-20
    • 2011-08-05
    • 1970-01-01
    • 2017-11-05
    相关资源
    最近更新 更多