领先一步
VMware 提供培训和认证,以加快您的进步。
了解更多这是我希望成为一系列小型文章的多部分系列的第一部分,这些文章展示了围绕 Spring Security 自定义的实用示例。这些自定义的需求并非虚构,而是全部来自实际应用……
假设您有以下需求。您有一个角色列表,每个角色包含适用于此角色的业务功能列表(见下文)
ROLE_ADMIN BF_QUOTE_CREATE BF_POLICY_CREATE BF_POLICY_DELETE
ROLE_AGENT BF_QUOTE_CREATE BF_POLICY_CREATE
ROLE_USER BF_QUOTE_CREATE
诀窍是能够基于两者做出授权决策。
例如:拥有 ROLE_ADMIN 角色的用户应被授予对受此角色保护的任何资源的访问权限。 <sec:authorize ifAllGranted="ROLE_ADMIN"> <p><a href="http://www.google.com">Google</a> </sec:authorize>
或 @Secured("ROLE_ADMIN") public void foo() . . . }
同一用户应被授予对受相应业务功能保护的任何资源的访问权限。 <sec:authorize ifAllGranted="BF_POLICY_DELETE"> <p><a href="http://www.google.com">Google</a> </sec:authorize>
或 @Secured("BF_POLICY_DELETE") public void foo() . . . }
实际上有几种方法可以处理此需求。其中一种方法是创建一个 RoleHierarchy 并使用 RoleHierarchyVoter 遍历角色层次结构。这种方法的缺点是,在 Spring Security 2.0.4 的当前实现中,标签库(security: authorize …)不会通过AccessDecisionManager 来做出决策,因此在做出关于保护 HTML 元素的决策时,Voter 不会发挥任何作用。但是,鉴于 Spring Security 惊人的灵活性和自定义能力,实现此需求仍然非常简单。Spring Security 最大的优势之一是围绕如何创建Principal(UserDetails 对象)的自定义。创建UserDetails 对象时,会使用GrantedAuthority 列表对其进行填充。稍后会检查此列表以匹配保护资源的GrantedAuthority。我们可以进行的自定义之一是在创建UserDetails 对象期间自定义GrantedAuthority 列表。
在提供的示例中,有两个属性文件(为了简化起见,我使用属性文件,但是您可以轻松地修改它以使用数据库或 LDAP)。一个文件users.properties 将用户映射到角色 oleg=powder,ROLE_ADMIN
而另一个文件role-to-bf.properties 将角色映射到业务功能列表 ROLE_ADMIN=BF_QUOTE_CREATE,BF_POLICY_CREATE,BF_POLICY_DELETE
我们的目标是创建一个UserDetails 对象,其中包含一个GrantedAuthority 列表,该列表表示角色和业务功能。例如:对于用户oleg,GrantedAuthority 列表应为: ROLE_ADMIN,BF_QUOTE_CREATE,BF_POLICY_CREATE,BF_POLICY_DELETE
因此,我们只需要定义 UserDetailsService 的自定义实现,在其中使用这两个属性文件(在现实生活中可能是数据库或 LDAP),我们将创建自定义的GrantedAuthority 列表,然后将它们注入到最终的UserDetails 对象中。这非常简单,我们可以重用GrantedAuthority 接口的现有实现,例如 GrantedAuthorityImpl。但是,我们还想确保我们可以跟踪(用于调试或任何其他目的)每个表示业务功能的 GrantedAuthority 的父GrantedAuthority。为了实现这两个目标,我们将通过定义一个BusinessFunctionGrantedAuthority 类来扩展GrantedAuthorityImpl,该类仅包含定义此类业务功能的所有父GrantedAuthority 对象的列表。 public class BusinessFunctionGrantedAuthority extends GrantedAuthorityImpl { private List<GrantedAuthority> parentAuthorities; . . . }
然后,我们将创建 UserDetailsService 的自定义实现并实现loadUserByName(..) 方法,在其中我们将执行以下操作
1. 基于 users.properties 文件的内容创建 UserAttribute 对象。UserAttribute 将包含表示角色的 GrantedAuthority 列表。
2. 遍历角色-GrantedAuthority 列表,并为每个角色-GrantedAuthority 创建一个 BusinessFunctionGrantedAuthority 并将其添加到已创建的 GrantedAuthority 的整体列表中
2.1 将父 GrantedAuthority 添加到每个 BusinessFunctionGrantedAuthority
3. 创建包含完整 GrantedAuthority 列表的最终 UserDetails 对象。
然后在您的 Spring Security 配置中定义您的AuthenticationProvider
注意:我们正在使用 ComplexAuthorityUserDetailsService 类实现的自定义 UserDetailsService 注入 AuthenticationProvider。(有关更多详细信息,请参阅示例代码)
保护您的资源,部署并访问应用程序:https://127.0.0.1:8080/spring-security-sample-grantedAuthority/index.jsp
登录后,您应该会看到显示的GrantedAuthority 列表以及Principal 的其他属性
您可以清楚地看到,表示业务功能的GrantedAuthority 也显示了定义此类业务功能的父GrantedAuthority 列表。检查index.jsp 并观察security:authorize 标签如何使用角色和业务功能来保护 HTML 元素。就是这样。您可以清楚地看到,通过一些小的自定义,您可以轻松地扩展和自定义 Principal 的结构,并基于自定义 GrantedAuthority 应用声明性保护,而无需使用自定义安全代码污染您的业务代码。
示例代码可以从此处下载:spring-security-sample-grantedauthority