书接上文,在经过了authentication后就是authorization了,本文还是对官网文档authorization的一个架构翻译和个人理解,后续的博客在写具体使用例子,从数据中认证,融合authentication和authorization的概念。
Authorization就是在确定用户进行身份验证的方式后,还需要配置应用进程的授权规则。
Spring Security 中之所以这么受到那么多人信任和欢迎,高级授权功能是其原因之一。无论您选择如何进行身份验证(无论是使用 Spring Security 提供的机制和提供进程,还是与容器或其他非 Spring Security 身份验证机构集成),都可以在应用进程中以一致且简单的方式使用授权服务。
应考虑把授权规则附加在请求 URIs路径和方法上。下面还有大量关于Spring Security授权如何工作的细节,以及在创建了基本模型后,如何对其进行微调。
Authentication讨论的是所有Authentication的实现,以及如何存储 GrantedAuthority 对象的列表。这些代表已授予委托人的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,在后续中由 AccessDecisionManager 实例读取来实现授权决策。
GrantedAuthority接口只有一个方法
String getAuthority();
AuthorizationManager 实例使用此方法来获取 GrantedAuthority 准确的字符串表示形式。通过以字符串形式返回表示形式,大多数 AuthorizationManager 实现可以轻松“读取”GrantedAuthority。如果 GrantedAuthority 无法准确表示为字符串,则 GrantedAuthority 被视为“复杂”,并且 getAuthority() 必须返回 null。
GrantedAuthority复杂的实现在于存储适用于不同客户帐号的操作和权限阈值列表。将这个复杂的 GrantedAuthority 表示为字符串将非常困难。因此,getAuthority() 方法应返回 null。这向任何 AuthorizationManager 表明它需要支持特定的 GrantedAuthority 实现才能理解其内容。
Spring Security 包含一种具体的 GrantedAuthority 实现:SimpleGrantedAuthority。此实现允许将任何用户指定的字符串转换为 GrantedAuthority。安全架构中包含的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。
默认情况下,基于角色的授权规则包含 ROLE_ 作为前缀。这意味着,如果有一个授权规则要求安全上下文具有“USER”角色,Spring Security 将默认查找返回“ROLE_USER”的 GrantedAuthority#getAuthority。
您可以使用 GrantedAuthorityDefaults 对此进行自定义。 GrantedAuthorityDefaults 的存在是为了允许自定义用于基于角色的授权规则的前缀。
您可以通过公开 GrantedAuthorityDefaults bean 将授权规则配置为使用不同的前缀,如下所示:
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
这边官网还有个提示:
可以使用静态方法公开 GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的方法安全性@Configuration类之前发布它
Spring Security 提供了拦截器来控制对安全对象(例如方法调用或 Web 请求)的访问。调用前决定是否允许调用继续由 AuthorizationManager 实例做出。此外,调用后是否可返回给定值的决策由 AuthorizationManager 实例做出。
AuthorizationManager 取代了 AccessDecisionManager 和 AccessDecisionVoter。
使用自定义 AccessDecisionManager 或 AccessDecisionVoter 的应用程序建议更改为使用 AuthorizationManager。AuthorizationManager 接口包含两种方法:
AuthorizationManager 是通过 Spring Security 基于请求、基于方法和基于消息的授权组件来调用,并负责做出最终的访问控制决策。AuthorizationManager 接口包含两种方法:
AuthorizationDecision check(Supplier authentication, Object secureObject);
default AuthorizationDecision verify(Supplier authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
AuthorizationManager 的检查方法将传递它需要的所有相关信息,以便做出授权决策。具体而言,传递安全对象可以检查实际安全对象调用中包含的那些参数。例如,假设安全对象是 MethodInvocation。很容易在 MethodInvocation 中查询任何 Customer 参数,然后在 AuthorizationManager 中实现某种安全逻辑,以确保允许主体对该客户进行操作。如果授予访问权限,则实现应返回正 AuthorizationDecision,如果访问被拒绝,则返回负 AuthorizationDecision,在弃权时返回空 AuthorizationDecision。
verify 调用检查,随后在负 AuthorizationDecision 的情况下引发 AccessDeniedException。
虽然用户可以实现自己的 AuthorizationManager 来控制授权的所有方面,但 Spring Security 附带了一个委托 AuthorizationManager,它可以与各个 AuthorizationManager 协作。
RequestMatcherDelegatingAuthorizationManager 会将请求与最合适的委托AuthorizationManager 进行匹配。为了方法安全性,可以使用 AuthorizationManagerBeforeMethodInterceptor 和 AuthorizationManagerAfterMethodInterceptor。
Authorization Manager Implementations阐释它的相关类。
使用此方法,可以在授权决策上轮询 AuthorizationManager 实现一系列组合。
Spring Security 提供的最常见的 AuthorizationManager 是 AuthorityAuthorizationManager。它配置了一组给定的权限,以在当前Authentication身份验证上查找。如果 Authentication 包含任何已配置的authorities,它将返回正 AuthorizationDecision。否则,它将返回负的 AuthorizationDecision。
另一个管理器是 AuthenticatedAuthorizationManager。它可用于区分匿名用户、完全身份验证用户和“记住我”身份验证的用户。许多站点允许在“记住我”身份验证下进行某些有限的访问,但要求用户通过登录来确认其身份才能获得完全访问权限。
AuthenticationManagers 中还有有用的静态工厂,用于将单个 AuthenticationManager 组合成更复杂的表达式。
显然,您还可以实现自定义 AuthorizationManager,并且可以将所需的任何访问控制逻辑放入其中。它可能特定于您的应用程序(与业务逻辑相关),也可能实现某些安全管理逻辑。例如,您可以创建一个可以查询 Open Policy Agent 或您自己的授权数据库的实现。
小提示:您可以在 Spring 网站上找到一篇博客文章,其中介绍了如何使用旧AccessDecisionVoter 实时拒绝帐户已被暂停的用户的访问。可以通过改为实现 AuthorizationManager 来实现相同的结果。
在 AuthorizationManager 之前,Spring Security 发布了 AccessDecisionManager 和 AccessDecisionVoter。
在某些情况下,例如迁移较旧的应用程序,可能需要引入调用 AccessDecisionManager 或 AccessDecisionVoter 的 AuthorizationManager。
要调用现有的 AccessDecisionManager, 可以如下:
@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
private final AccessDecisionManager accessDecisionManager;
private final SecurityMetadataSource securityMetadataSource;
@Override
public AuthorizationDecision check(Supplier authentication, Object object) {
try {
Collection attributes = this.securityMetadataSource.getAttributes(object);
this.accessDecisionManager.decide(authentication.get(), object, attributes);
return new AuthorizationDecision(true);
} catch (AccessDeniedException ex) {
return new AuthorizationDecision(false);
}
}
@Override
public void verify(Supplier authentication, Object object) {
Collection attributes = this.securityMetadataSource.getAttributes(object);
this.accessDecisionManager.decide(authentication.get(), object, attributes);
}
}
然后将其连接到您的 SecurityFilterChain。
或者,若要仅调用 AccessDecisionVoter,可以执行以下操作:
@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
private final AccessDecisionVoter accessDecisionVoter;
private final SecurityMetadataSource securityMetadataSource;
@Override
public AuthorizationDecision check(Supplier authentication, Object object) {
Collection attributes = this.securityMetadataSource.getAttributes(object);
int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
switch (decision) {
case ACCESS_GRANTED:
return new AuthorizationDecision(true);
case ACCESS_DENIED:
return new AuthorizationDecision(false);
}
return null;
}
}
然后将其连接到您的安全过滤链中。
应用程序的通常需求,一个特定角色应自动“包含”其他角色。例如,在具有“管理员”和“用户”角色概念的应用程序中,您可能希望管理员能够执行普通用户所能做的所有事情。为此,您可以确保所有管理员用户也被分配了“用户”角色。或者,您可以修改每个访问约束,要求“用户”角色也包括“管理员”角色。如果您的应用程序中有很多不同的角色,这可能会变得非常复杂。
使用角色层次结构可以配置哪些角色(或权限)应包括其他角色。Spring Security 的 RoleVoter 的扩展版本 RoleHierarchyVoter 配置了 RoleHierarchy,它从中获取分配给用户的所有“可访问权限”。典型配置可能如下所示:
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
return hierarchy;
}
// and, if using method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
温馨提示:RoleHierarchy Bean 配置尚未移植到 @EnableMethodSecurity。因此,此示例使用的是 AccessDecisionVoter。如果需要 RoleHierarchy 支持方法安全性,请继续使用 @EnableGlobalMethodSecurity,直到 github.com/spring-projects/spring-security/issues/12783 完成。
在这里,我们在层次结构ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST中有四个角色。使用 ROLE_ADMIN 进行身份验证的用户在针对适用于调用上述 RoleHierarchyVoter 的 AuthorizationManager 评估安全约束时,其行为将被视为具有所有四个角色。>符号可以被认为是“包括”的意思。
角色层次结构提供了一种方便的方法,可以简化应用程序的访问控制配置数据和/或减少需要分配给用户的权限数量。对于更复杂的要求,您可能希望在应用程序所需的特定访问权限和分配给用户的角色之间定义逻辑映射,以便在加载用户信息时在两者之间进行转换。
Spring Security 包含一些遗留组件。由于它们尚未删除,因此出于历史目的包含文档。他们推荐的替代品如上所述。
AccessDecisionManager 由 AbstractSecurityInterceptor 调用,负责做出最终的访问控制决策。AccessDecisionManager 接口包含三种方法:
void decide(Authentication authentication, Object secureObject,
Collection attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
AccessDecisionManager 的 decide 方法传递了做出授权决策所需的所有相关信息。具体而言,传递安全对象可以检查实际安全对象调用中包含的那些参数。例如,假设安全对象是 MethodInvocation。您可以在 MethodInvocation 中查询任何 Customer 参数,然后在 AccessDecisionManager 中实现某种安全逻辑,以确保允许主体对该客户进行操作。如果访问被拒绝,则实现应引发 AccessDeniedException。
supports(ConfigAttribute) 方法由 AbstractSecurityInterceptor 在启动时调用,以确定 AccessDecisionManager 是否可以处理传递的 ConfigAttribute。supports(Class) 方法由安全侦听器实现调用,以确保配置的 AccessDecisionManager 支持安全侦听器提供的安全对象类型。
虽然用户可以实现自己的 AccessDecisionManager 来控制授权的所有方面,但 Spring Security 包含多个基于投票的 AccessDecisionManager 实现。投票决策管理器描述了相关类。
下图显示了 AccessDecisionManager 接口:
通过使用此方法,将针对授权决策轮询一系列 AccessDecisionVoter 实现。然后,AccessDecisionManager 根据其对投票的评估决定是否引发 AccessDeniedException。
AccessDecisionVoter 接口有三种方法:
int vote(Authentication authentication, Object object, Collection attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
具体实现返回一个 int,可能的值反映在名为 ACCESS_ABSTAIN、ACCESS_DENIED 和 ACCESS_GRANTED 的 AccessDecisionVoter 静态字段中。如果投票实现对授权决策没有反对,则返回 ACCESS_ABSTAIN。如果它确实有反对意见,它必须返回ACCESS_DENIED或ACCESS_GRANTED。
Spring Security 提供了三个具体的 AccessDecisionManager 实现来统计投票。ConsensusBased 实现根据非弃权票的共识授予或拒绝访问。提供属性以控制在票数相等或所有票数均弃权的情况下的行为。如果收到一个或多个ACCESS_GRANTED投票,则 AffirmativeBased 实现将授予访问权限(换句话说,如果至少有一个授予投票,则将忽略拒绝投票)。与 ConsensusBased 实现一样,如果所有投票者都弃权,则有一个参数来控制行为。UnanimousBased 提供程序期望获得一致的 ACCESS_GRANTED 票才能授予访问权限,而忽略了弃权票。如果有任何ACCESS_DENIED投票,它将拒绝访问。与其他实现一样,有一个参数用于控制所有投票者弃权时的行为。
您可以实现一个自定义 AccessDecisionManager,它以不同的方式计算投票。例如,来自特定 AccessDecisionVoter 的投票可能会获得额外的权重,而来自特定投票者的拒绝投票可能会产生否决权效应。
Spring Security 提供的最常用的 AccessDecisionVoter 是 RoleVoter,它将配置属性视为角色名称,并在用户被分配了该角色时投票授予访问权限。
如果任何 ConfigAttribute 以 ROLE_ 前缀开头,它将投票。如果存在一个 GrantedAuthority,它返回的 String 表示形式(来自 getAuthority() 方法)与一个或多个以 ROLE_ 前缀开头的 ConfigAttributes 完全相等,则它会投票授予访问权限。如果任何以 ROLE_ 开头的 ConfigAttribute 没有完全匹配,则 RoleVoter 将投票拒绝访问。如果没有 ConfigAttribute 以 ROLE_ 开头,则投票者弃权。
我们隐式看到的另一个投票者是 AuthenticatedVoter,它可用于区分匿名、完全身份验证和记住我身份验证的用户。许多站点允许在“记住我”身份验证下进行某些有限的访问,但要求用户通过登录来确认其身份才能获得完全访问权限。
当我们使用 IS_AUTHENTICATED_ANONYMOUSLY 属性授予匿名访问权限时,此属性正在由 AuthenticatedVoter 处理。有关详细信息,请参阅 AuthenticatedVoter。
您还可以实现自定义 AccessDecisionVoter,并在其中放置所需的任何访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),也可能实现某些安全管理逻辑。例如,在 Spring 网站上,您可以找到一篇博客文章,该文章介绍了如何使用投票者实时拒绝帐户已被暂停的用户的访问。
与 Spring Security 的许多其他部分一样,AfterInvocationManager 有一个具体的实现,即 AfterInvocationProviderManager,它轮询 AfterInvocationProviders 列表。允许每个 AfterInvocationProvider 修改返回对象或引发 AccessDeniedException。事实上,多个提供程序可以修改对象,因为前一个提供程序的结果将传递给列表中的下一个提供程序。
请注意,如果您使用的是 AfterInvocationManager,您仍然需要允许 MethodSecurityInterceptor 的 AccessDecisionManager 允许操作的配置属性。如果您使用的是典型的 Spring Security 包含的 AccessDecisionManager 实现,则没有为特定的安全方法调用定义配置属性将导致每个 AccessDecisionVoter 弃权。反过来,如果 AccessDecisionManager 属性“allowIfAllAbstainDecisions”为 false,则将引发 AccessDeniedException。您可以通过以下任一方式避免此潜在问题:(i) 将“allowIfAllAbstainDecisions”设置为 true(尽管通常不建议这样做)或 (ii) 仅确保 AccessDecisionVoter 将投票授予访问权限的至少一个配置属性。后一种(推荐)方法通常通过ROLE_USER或ROLE_AUTHENTICATED配置属性来实现。
《Spring bott官网》