Spring 安全架构 - 身份验证与访问控制

应用程序安全性归结为或多或少的两个独立问题:身份验证(你是谁?)和授权(你可以做什么?)。有时人们会说 “访问控制” 而不是 “授权”,这可能会造成混淆,但是以这种方式思考可能会有所帮助,因为 “授权” 在其他地方超载。Spring Security 的体系结构旨在将身份验证与授权分开,并具有策略及扩展点。

身份验证

身份验证的主要策略接口是 AuthenticationManager,它只有一个方法:

public interface AuthenticationManager {

  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;

}

AuthenticationManager 可以在 authenticate() 方法中执行以下三项操作之一:

  1. 如果它可以验证输入是否代表有效的主体,则返回 Authentication(通常伴随 authenticated=true);
  2. 如果认为输入代表无效的主体,则抛出 AuthenticationException
  3. 如果无法确定,则返回 null

AuthenticationException 是运行时异常。它通常由应用以通用方式处理,具体取决于应用的样式或目的。换句话说,通常不希望用户代码捕获并处理它。例如,一个 Web UI 将呈现一个页面,该页面指出身份验证失败,而后端 HTTP 将发送 401 响应,取决于上下文,带有或不带有 WWW-Authenticate 标头。

AuthenticationManager 最常用的实现是 ProviderManager,它委派了 AuthenticationProvider 实例链。AuthenticationProvider 有点像 AuthenticationManager,但是它还有一个额外的方法,允许调用者查询是否支持给定的 Authentication 类型:

public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;

	boolean supports(Class<?> authentication);

}

supports() 方法中的 Class<?> 参数实际上是 Class<? extends Authentication>(仅会询问它是否支持将传递到 authenticate() 方法中的内容)。通过委派给 AuthenticationProviders 链,ProviderManager 可以在同一应用中支持多种不同的身份验证机制。如果 ProviderManager 无法识别特定的身份验证实例类型,则将跳过该类型。

ProviderManager 具有可选的父级,如果所有供应程序都返回 null,则可以咨询该父级。如果父级不可用,则 null Authentication 将导致 AuthenticationException

有时,应用具有逻辑组的受保护资源(例如,与路径模式 /api/** 匹配的所有 Web 资源),并且每个组可以具有自己的专用 AuthenticationManager。通常,每一个都是 ProviderManager,它们共享一个父级。因此,父级是一种 “全局” 资源,充当所有供应程序的后备。

图片 1. 使用 ProviderManagerAuthenticationManager 层次结构

自定义身份验证管理器

Spring Security 提供了一些配置助手,可以快速获取在应用中设置的通用身份验证管理器功能。最常用的帮助程序是 AuthenticationManagerBuilder,它非常适合设置内存中的 JDBC 或 LDAP 用户详细信息,或者用于添加自定义 UserDetailService。这是配置全局(父)AuthenticationManager 的应用的示例:

@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

   ... // web stuff here

  @Autowired
  public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
    builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
      .password("secret").roles("USER");
  }

}

该示例与 Web 应用有关,但是 AuthenticationManagerBuilder 的用法更为广泛(有关如何实现 Web 应用安全性的详细信息,请参见下文)。请注意,AuthenticationManagerBuilder@Autowired@Bean 的方法中的 - 这就是使它构建全局(父)AuthenticationManager 的原因。相反,如果我们这样做的话:

@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

  @Autowired
  DataSource dataSource;

   ... // web stuff here

  @Override
  public void configure(AuthenticationManagerBuilder builder) {
    builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
      .password("secret").roles("USER");
  }

}

(在配置程序的方法中使用 @Override),那么 AuthenticationManagerBuilder 仅用于构建 “本地” AuthenticationManager,它是全局控件的子级。在 Spring Boot 应用中,你可以 @Autowired,将全局的一个 bean 倒入另一个。但是除非你自己显示公开它,否则不能对本地的 Bean 执行该操作。

Spring Boot 提供了一个默认的全局 AuthenticationManager(只有一个用户),除非你通过提供自己的 AuthenticationManager 类型的 bean 来抢占它。除非你主动需要自定义的全局 AuthenticationManager,否则默认值本身就足够安全,你不必担心太多。如果执行任何构建 AuthenticationManager 的配置,则通常可以在本地对要保护的资源进行配置,而不必担心全局默认值。

授权或访问控制

身份验证成功后,我们可以继续进行授权,这里的核心策略是 AccessDecisionManager。框架提供了三种实现,所有三个实现都委托给 AccessDecisionVoter 链,有点像 ProviderManager 委托给 AuthenticationProviders

AccessDecisionVoter 考虑使用 ConfigAttributes 修饰的 Authentication(代表主体)及安全 Object

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

int vote(Authentication authentication, S object,
        Collection<ConfigAttribute> attributes);

ObjectAccessDecisionManagerAccessDecisionVoter 的签名中是完全通用的 - 它表示用户可能想要访问的任何内容(Web 资源或 Java 类中的方法是两种最常见的情况)。ConfigAttributes 也相当通用,用一些元数据来表示安全 Object 的修饰,这些元数据确定访问它所需的权限级别。ConfigAttribute 是一个接口,但是它只是有一个非常通用的方法,并返回一个 String,因此这些字符串以某种方式编码资源所有者的意图,表达有关允许谁访问它的规则。典型的 ConfigAttribute 是用户角色的名称(如 ROLE_ADMINROLE_AUDIT),并且它们通常具有特殊的格式(如 ROLE_ 前缀)或表示需要求值的表达式。

大多数人只使用默认的 AccessDecisionManager,它是 AffirmativeBased 的(如果任何选民肯定地返回,则将授予访问权限)。任何定制都倾向于在选民中发生,要么添加新选民,要么修改现有选民的工作方式。

使用 Spring 表达式语言(SpEL)表达式的 ConfigAttribute 非常常见,例如 isFullyAuthenticated() && hasRole('FOO')AccessDecisionVoter 支持该功能,可以处理表达式并为其创建上下文。为了扩展可以处理的表达式的范围,需要 SecurityExpressionRoot 的自定义实现,有时还需要 SecurityExpressionHandler

发布了228 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/stevenchen1989/article/details/105172808
今日推荐