Authentication

Authentication 讨论了所有 Authentication 实现如何存储 GrantedAuthority 对象的列表。这些对象代表已经授予委托人(principal)的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,随后由 AccessDecisionManager 实例在做出授权决定时读取。

GrantedAuthority 接口只有一个方法。

String getAuthority();

Copied!

这个方法被 AuthorizationManager 实例用来获取 GrantedAuthority 的一个精确的 String 表示。通过返回一个 String 表示,一个 GrantedAuthority 可以被大多数 AuthorizationManager 实现轻松 "读取"。如果 GrantedAuthority 不能被精确地表示为一个 String,那么该 GrantedAuthority 被认为是 "复杂的",getAuthority() 必须返回 null

一个复杂的 GrantedAuthority 的例子是一个实现,它存储了一个适用于不同客户账号的操作和权限阈值的列表。将这种复杂的 GrantedAuthority 表示为一个 String 将是相当困难的。因此,getAuthority() 方法应该返回 null。这向任何 AuthorizationManager 表明,它需要支持特定的 GrantedAuthority 实现来理解其内容。

Spring Security 包括一个具体的 GrantedAuthority 实现。SimpleGrantedAuthority。这个实现允许任何用户指定的字符串被转换为 GrantedAuthority。安全架构中包含的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

默认情况下,基于角色的授权规则包括 ROLE_ 作为前缀。这意味着,如果有一个授权规则要求 security context 的角色是 "USER",Spring Security 将默认寻找返回 "ROLE_USER" 的 GrantedAuthority#getAuthority

你可以用 GrantedAuthorityDefaults 来定制这个。GrantedAuthorityDefaults 的存在是为了允许自定义基于角色的授权规则所使用的前缀。

你可以通过暴露一个 GrantedAuthorityDefaults Bean 来配置授权规则以使用不同的前缀,像这样:

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}

调用处理

Spring Security 提供的拦截器可以控制对安全对象的访问,例如方法调用或Web请求。关于调用是否被允许进行的预调用决定是由 AuthorizationManager 实例做出的。同样,关于是否可以返回给定值的调用后决策也是由 AuthorizationManager 实例做出的。

AuthorizationManager

AuthorizationManager 同时取代了 AccessDecisionManagerAccessDecisionVoter

我们鼓励定制 AccessDecisionManagerAccessDecisionVoter 的应用程序 改为使用 AuthorizationManager

AuthorizationManager 被 Spring Security 的 基于请求基于方法基于消息 的授权组件所调用,并负责做出最终的访问控制决定。AuthorizationManager 接口包含两个方法:

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

Copied!

AuthorizationManager 的检查方法被传递给它所需要的所有相关信息,以便做出授权决定。特别是,传递安全对象(secure Object)使那些包含在实际安全对象调用中的参数能够被检查到。例如,让我们假设安全对象是一个 MethodInvocation。查询 MethodInvocation 的任何客户参数是很容易的,然后在 AuthorizationManager 中实现某种安全逻辑以确保委托人(principal)被允许对该客户进行操作。如果访问被授予,实现应返回 "negative" 的 AuthorizationDecision,如果访问被拒绝,应返回 "negative" 的 AuthorizationDecision,如果不作出决定,则返回空的 AuthorizationDecision。

verify 调用 check,然后在出现 "negative" 的 AuthorizationDecision 决定时抛出一个 AccessDeniedException。

基于授权的AuthorizationManager实现

虽然用户可以实现他们自己的 AuthorizationManager 来控制授权的所有方面(aspect),但Spring Security提供了一个委托的 AuthorizationManager,可以与个别的 AuthorizationManager 协作。

RequestMatcherDelegatingAuthorizationManager 将把请求与最合适的委托(delegate) AuthorizationManager 相匹配。 对于方法安全,你可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

authorizationhierarchy

使用这种方法,AuthorizationManager 实现的组合可以在授权决定上被轮询。

AuthorityAuthorizationManager

Spring Security提供的最常见的 AuthorizationManagerAuthorityAuthorizationManager。它被配置为在当前 Authentication 中寻找一组给定的授权。如果 Authentication 包含任何配置的授权,它将返回 positive 的 AuthorizationDecision。否则,它将返回一个 negative 的 AuthorizationDecision

AuthenticatedAuthorizationManager

另一个管理器是 AuthenticatedAuthorizationManager。它可以用来区分匿名、完全认证和记住我认证的用户。许多网站在Remember-me认证下允许某些有限的访问,但要求用户通过登录来确认他们的身份以获得完整的访问。

AuthenticationManagers

AuthenticationManagers 中还有一些有用的静态工厂,用于将单个 AuthenticationManagers 组合成更复杂的表达式。

自定义授权管理器(Authorization Manager)

很明显,你也可以实现一个自定义的 AuthorizationManager,你可以把你想要的任何访问控制逻辑放在里面。它可能是针对你的应用程序的(与业务逻辑有关),也可能实现一些安全管理逻辑。例如,你可以创建一个可以查询Open Policy Agent或你自己的授权数据库的实现。

package com.example.eachadmin.config.Authorization;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;


import java.util.Set;
import java.util.function.Supplier;

@Component
public class RBACAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
        Authentication auth = authentication.get();
        boolean authenticated = auth.isAuthenticated();
        Set<Object> urls = redisTemplate.opsForSet().members("mySet");
        if (!authenticated) return new AuthorizationDecision(false);
        if (urls != null) {
            return new AuthorizationDecision(urls.contains(object.getRequest().getRequestURI()));
        }
        return new AuthorizationDecision(false);
    }
}
@Component
public class CustomizeAuthorizeHttpRequestsConfig implements Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> {

    @Autowired
    private RBACAuthorizationManager rbacAuthorizationManager;

    @Override
    public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry authorizationManagerRequestMatcherRegistry) {
        authorizationManagerRequestMatcherRegistry
                .requestMatchers("/login", "/image")
                .permitAll()
                .anyRequest()
                .access(rbacAuthorizationManager);
        }
}