Spring Security 自定义 (第二部分 - 实时调整安全会话)

工程 | Oleg Zhurakousky | 2009年1月3日 | ...

想象一下,你正处于一个安全会话中(你已登录并被授权访问特定资源),但你的安全基础设施团队更新了你的权限和特权。也许你被授予了更多权限和特权,也许你的权限被完全撤销了... 问题在于,你的安全会话在会话注册表中已经注册,在你注销/重新登录之前,代表你在该安全会话中的主体 (Principal) 将不会被重新创建。更戏剧性的是什么情况(毕竟我们这里讨论的是安全问题)... 你是一名心怀不满的员工,你的直属管理层发现了你的“不当行为”,但你的公司需要开5个会议和提交10份审批表才能处理此事,在此期间你是否可以自由地造成更多损害???

显然,有许多业务场景可能需要在用户处于会话期间调整用户的权限。在 Spring Security 中,显然也有不止一种方法可以实现这一点。一种方法是利用会话注册表(它实际上是用于并发会话控制的)将用户踢出系统。通过构建一个 GUI 或启用 JMX,可以从已注册会话列表中“使”某个用户“过期”。他们发起的下一个请求将被拒绝,会话将失效,从而强制进行身份验证过程。然而,为了更有趣地演示实现类似目标的另一种方法,让我们稍微改变一下场景,假设我们不想完全使用户的安全会话失效,而只是想暂时挂起它。

现在,挂起 (suspend) 这个词在安全的语境中可能意味着很多不同的事情。它可以表示临时挂起,也可以表示完全撤销权利。我将把这个词在任何上下文中的具体含义留给你自己定义... 我更愿意做的是向你展示如何使用 Spring Security 组件,如 AccessDecisionVoterAccessDecisionManager,来实时调整安全会话。在下面的例子中,我们还将通过暂时挂起用户的权利而不使其会话失效的方式来调整当前的安全会话。

为了实现这一点,我们需要遵循以下步骤。

1. 在 Spring Security 配置中定义 AccessDecisionManager     1.1 定义基本的投票器,例如 RoleVoter 和 AuthenticatedVoter 2. 定义并实现 AccessDecisionVoter。这个投票器必须维护一个被撤销权利的用户列表,并且在用户执行安全操作时必须投 ACCESS_DENIED 票。 3. 将这个投票器添加到 AccessDecisionManager 已注册的投票器堆栈中。

此外,为了能够在应用程序运行时与该投票器进行交互,我们还将使用 Spring JMX 将该投票器动态导出为 JMX Bean,从而启用其 JMX 功能。

基本就是这样。

因此,首先我们需要定义 AccessDecisionManager (ADM)。由于我们使用了 Spring Security 命名空间(只要可能),像 "security:http" 这样的配置元素,Spring Security 会为我们注册默认的 AccessDecisionManager,它碰巧是 AffirmativeBased ADM。这对我们来说不太好,因为我们在这里希望更保守一些。所以,我们要做的就是定义一个 UnanimousBased ADM,并使用指向 UnanimousBased ADM 的 access-decision-manager-ref 属性覆盖 "security:http" 元素的默认 ADM(见下文)。

<!-- 定义自定义投票器 --> <bean id="suspendVoter" class="org.springframework.security.sample.SuspendRealTimeVoter"/> <!-- 将 AccessDecisionManager 定义为 UnanimousBased --> <bean id="accessDecisionManager" class="org.springframework.security.vote.UnanimousBased">   <property name="decisionVoters">     <list>       <ref bean="suspendVoter" />       <bean class="org.springframework.security.vote.RoleVoter" />       <bean class="org.springframework.security.vote.AuthenticatedVoter" />     </list>   </property> </bean>

我们还将把自定义投票器放在投票器堆栈中的首位,因为我们知道在 UnanimousBased(一致通过)的决策过程中,第一个 NO 意味着不应进行进一步评估。意识到其他投票决策可能耗时或影响性能,将自定义投票器首先注册到投票器堆栈中可以确保只有在权限未被挂起时才执行其他投票操作。

<!-- 定义 http 安全配置 --> <security:http access-decision-manager-ref="accessDecisionManager" . . . . .> . . . . </security:http>

剩下的唯一事情是 JMX 启用 "suspendVoter"。这使用 Spring JMX 很容易完成。 <!-- JMX 启用自定义投票器 --> <bean class="org.springframework.jmx.export.MBeanExporter">   <property name="beans">     <map>       <entry key="org.springframework.security:name=SuspendRealTimeVoter" value-ref="suspendVoter" />     </map>   </property> </bean>

现在,"suspendVoter" 将被导出到 JMX 服务器中,名称为 org.springframework.security:name=SuspendRealTimeVoter

从代码的角度来看,我们唯一需要实现的自定义组件是 SuspendRealTimeVoter 类。在该类中,我们将维护一个用户 Set,这些用户的权利已被临时撤销。在 vote(..) 方法内部,我们的逻辑相当简单。如果用户在列表中,则投 ACCESS_DENIED 票,否则投 ACCESS_GRANTED 票。

public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {     String userName = authentication.getName();     return revokedUsers.contains(userName) ? ACCESS_DENIED : ACCESS_GRANTED; }

在那里,你还会看到 suspend(String userName)grant(String userName) 方法,我们将通过它们来测试此功能。

注意:要通过 JMX 与我们的投票器进行交互,我们将使用你的 JDK (1.5+) 提供的 JConsole 工具,因此需要为我们的应用服务器启用 JMX。在这个例子中,我使用的是 Tomcat,所以如果你也使用 Tomcat,请在其启动脚本中添加以下 VM 参数

-Dcom.sun.management.jmxremote

启动服务器,部署应用程序并访问其 URL: http://localhost:8080/spring-security-sample-suspendUser

成功登录后,点击几次刷新,查看你的会话是否仍然活跃并正常运行。然后导航到你的 JDK 的 bin 目录,打开命令行并输入 jconsole(见下文)

JConsole 打开后,点击 MBeans 选项卡。浏览树并访问 SuspendRealTimeVoter。

你会看到 Operations 选项卡下提供了 suspend(..)grant(..) 方法。挂起你当前登录用户的权利,然后刷新你所在的 HTML 页面。你将看到 denied.html 页面。通过调用该用户的 grant(..) 方法取消挂起,你就可以恢复正常。现在,如果真的需要开5个会议和提交10份审批表,你可以临时撤销用户的权利,给你的经理们足够的时间来做出此类决定。

结论

这只是一个例子,演示了如何使用投票器临时挂起安全会话。但我希望你能清楚地看到,同样的方法也可以用于实现其他目标。这些目标可以是自动重新认证用户,或者仅仅更新其 GrantedAuthorities 列表等等。

本文的示例源代码可在此下载:spring-security-sample-suspenduser

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

快人一步

VMware 提供培训和认证,助你快速前进。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部