Fork me on GitHub
余鸢

Spring Security架构(一)

本指南是Spring Security的一个入门,介绍了框架的设计和基本构建块。 我们只介绍应用程序安全性的基础知识,但是这样做可以清除开发人员使用Spring Security时遇到的一些混乱。 为此,我们将了解在Web应用程序中应用安全性的方法使用过滤器以及更普遍地使用方法注解。使用本指南时,你需要了解在一个高层次的安全应用程序如何工作,以及如何可以定制,或者如果你只需要学习如何考虑应用程序的安全性。

本指南不是作为解决比最基本的问题(有其他来源的那些)的手册或谱方,但它可能对初学者和专家都很有用。Spring Boot 也提到了很多,因为它提供了一个安全的应用程序的一些默认的行为,它是有用的,以了解如何与整体架构结合使用。 所有这些原则同样适用于不使用Spring Boot的应用程序。

认证和访问控制

应用程序安全归结为两个或多或少独立的问题:authentication(你是谁?)和authorization(你允许做什么?)。 有时人们会说“访问控制”而不是“authorization”,这可能会让人困惑,但是,因为“authorization”对于在其他地方超载很有帮助。 Spring Security具有一种架构,旨在将身份验证与授权分开,并为两者提供策略和扩展点。

Authentication

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

1
2
3
4
5
6
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}

AuthenticationManager可以在其authenticate()方法中做3件事之一:

  1. 返回一个Authentication(通常与authenticated = true),如果它可以验证该输入表示为有效主体。
  2. 如果它认为输入表示为无效的主体,则抛出AuthenticationException

  3. 如果它不能判断则返回null

AuthenticationException是一个运行时异常。 它通常由应用程序以通用方式处理,这取决于应用程序的风格或目的。 换句话说,用户代码通常不希望捕获和处理它。 例如,Web UI将呈现表示认证失败的页面,并且后端HTTP服务将发送401响应,具有或不具有取决于上下文的WWW-Authenticate header。

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

1
2
3
4
5
6
7
8
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}

supports()方法中的Class<?>参数实际上是Class<? extends Authentication>(它将只被询问是否支持将被传递到authenticate()方法)。 ProviderManager可以通过代理一个AuthenticationProviders链在同一个应用程序中支持多种不同的认证机制。 如果ProviderManager不能识别特定的Authentication 实例类型,它将被跳过。

ProviderManager具有可选父级,如果所有提供程序都返回null,它可以查询。 如果父级 不可用,那么null Authentication将导致AuthenticationException

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

2

图1.使用ProviderManagerAuthenticationManager层次结构

自定义身份验证管理器

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

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
... // web stuff here
@Autowired
public initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}

此示例涉及Web应用程序,但AuthenticationManagerBuilder的用法更广泛适用(有关如何实施Web应用程序安全性的更多详细信息,请参见下文)。 注意,AuthenticationManagerBuilder@Autowired@Bean中的一个方法 - 这是什么使它构建全局 (父级) AuthenticationManager。 相反,如果我们这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
DataSource dataSource;
... // web stuff here
@Override
public configure(AuthenticationManagerBuilder builder) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}

(使用configurer中的@Override方法),那么AuthenticationManagerBuilder仅用于构建一个“local”AuthenticationManager,它是全局子节点的子节点。 在Spring Boot应用程序中,@Autowired全局的一个到另一个bean,但是你不能用本地的,除非你显式地暴露它自己。

Spring Boot提供了一个默认的全局AuthenticationManager(只有一个用户),除非你通过提供自己的AuthenticationManager类型bean来预先处理它。 默认是足够安全的,它自己你不必担心它,除非你主动需要一个自定义的全局AuthenticationManager。 如果你构建任何AuthenticationManager的配置,通常可以在本地保护您的资源,而不必担心全局默认。

授权或访问控制

一旦认证成功,我们可以继续授权,这里的核心策略是AccessDecisionManager。有三个实现提供的框架和三个委托到一个DecisionVoter链,有点像ProviderManager委托给AuthenticationProvidersDecisionVoter认为Authentication(代表主体)和用ConfigAttributes装饰的安全对象,对象在AccessDecisionManagerDecisionVoter的签名中是完全通用的 - 它表示用户可能想要访问的任何内容(Web类或Java类中的方法是两种最常见的情况)。 ConfigAttributes也是相当通用的,表示安全Object的装饰,其中一些元数据确定访问它所需的权限级别。 ConfigAttribute是一个接口,但它只有一个方法是非常通用的,并返回一个String,所以这些字符串以某种方式编码资源所有者的作用,表示有关谁被允许访问它的规则。典型的ConfigAttribute是用户角色的名称(如ROLE_ADMINROLE_AUDIT),它们通常具有特殊格式(如ROLE_prefix)或表示需要计算的表达式。

大多数人只是使用默认的AccessDecisionManagerAffirmativeBased(如果没有voters拒绝访问会被授予)。 任何定制都倾向于在voters中发生,或者添加新的定制,或者修改现有的定制方式。

使用作为Spring表达式语言(Spel)表达式的ConfigAttributes是非常常见的,例如isFullyAuthenticated() && hasRole('FOO')。 这由DecisionVoter支持,它可以处理表达式并为其创建上下文。 要扩展可处理的表达式的范围,需要自定义实现SecurityExpressionRoot,有时还需要SecurityExpressionHandler

Web Security

Web层中的Spring Security(对于UI和HTTP后端)是基于Servlet Filters的,所以一般先查看一下Filters的作用是很有帮助的。 下图显示了单个HTTP请求的处理程序的典型分层。

3

客户端向应用程序发送请求,容器根据请求URI的路径决定应用哪个filters和哪个servlet。最多只有一个servlet可以处理一个请求,但filters 形成产业链,所以他们是有序的,而事实上,如果要处理该请求本身的filters 可以否决链的其余部分。filters还可以修改下游过滤器和servlet中使用的请求和/或响应。过滤器链的顺序非常重要,Spring Boot通过两种机制来管理它:一个是@Beans类型Filter可以有一个@Order或者实现Ordered,另一个是它们可以是FilterRegistrationBean的一部分,作为其API的一部分本身具有顺序。一些现成的过滤器定义他们自己的常量,以帮助指示他们喜欢相对于彼此的顺序(例如,来自Spring Session的SessionRepositoryFilter具有一个为Integer.MIN_VALUE + 50DEFAULT_ORDER,它告诉我们它喜欢在早期的链,但不排除其他过滤器之前)。

Spring Security作为单个过滤器安装在链中。 在Spring Boot应用程序中,安全过滤器是ApplicationContext中的一个@Bean,默认情况下会安装它,以便将其应用于每个请求。 它安装在由SecurityProperties.DEFAULT_FILTER_ORDER定义的位置,该位置又由FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER(Spring Boot应用程序期望过滤器在包装请求时修改其行为的最大顺序)锚定。 还有更多的东西:从容器的角度来看Spring Security是一个单一的过滤器,但里面有额外的过滤器,每个扮演一个特殊的角色。 这里是一张图片:

4

图2. Spring Security是一个单一的物理过滤器,但将处理委托给一系列内部过滤器
实际上,在安全过滤器中甚至还有一层间接:它通常作为DelegatingFilterProxy安装在容器中。 代理委托给一个FilterChainProxy,它本身是一个@Bean,通常有一个固定名称的springSecurityFilterChain。 它是FilterChainProxy,它包含所有安全逻辑内部排列为一个链(或链)。 所有的过滤器都有相同的API(它们都实现了Servlet规范中的Filter接口),并且它们都有机会否决链的其余部分。

可以由Spring Security和所有未知的容器管理多个过滤链。 Spring Security过滤器包含过滤器链的列表,并将每个请求分派给匹配的第一个链。 下图显示了基于匹配请求路径(/foo/** 匹配/** )之前发生的分派。 这是非常常见的,但不是匹配请求的唯一方法。 这个分派进程的最重要的特征是只有一个链处理请求。

5

图3. Spring Security FilterChainProxy将请求分派给匹配的第一个链。
没有自定义安全配置的Spring Boot应用程序有几个(称为n)过滤器链,其中通常n = 6。 第一个(n-1)链在那里只是忽略静态资源模式,如/css/**/images/**和错误视图/error(路径可以由用户使用SecurityPropertiessecurity.ignored配置bean)。 最后一个链匹配catch所有路径/**,并且更活跃,包含用于认证,授权,异常处理,会话处理,头部写入等的逻辑。默认情况下,这个链中总共有11个过滤器,但通常 对于用户来说不需要关心使用哪个过滤器以及什么时候。

注意:事实上,Spring Security内部的所有过滤器对于容器都是未知的,这一点很重要,尤其是在Spring Boot应用程序中,默认情况下所有Filter类型的@Beans都会自动注册到容器中。 因此,如果要向安全链添加自定义过滤器,则需要不将其作为@Bean或将其包装在明确禁用容器注册的FilterRegistrationBean中。

注:翻译自https://spring.io/guides/topicals/spring-security-architecture/