本指南是 Spring Security 的入门指南,提供对框架设计和根本构建块的深刻理解。咱们仅涵盖应用程序平安的基础知识。然而,这样做,咱们能够革除应用 Spring Security 的开发人员遇到的一些困惑。为此,咱们通过应用过滤器,更个别地,通过应用办法注解,来看看在 Web 应用程序中利用安全性的形式。当您须要深刻理解平安应用程序的工作原理、如何对其进行自定义或须要学习如何思考应用程序安全性时,请应用本指南。
本指南并非旨在作为解决最根本问题的手册或秘诀(这些问题还有其余起源),但它对初学者和专家都可能有用。Spring Boot 也常常被援用,因为它为平安应用程序提供了一些默认行为,并且理解它如何与整体架构相适应会很有用。
所有准则同样实用于不应用 Spring Boot 的应用程序。
身份验证和访问控制
应用程序安全性归结为两个或多或少独立的问题:身份验证(你是谁?)和受权(你能够做什么?)。有时人们会说“访问控制”而不是“受权”,这可能会让人感到困惑,但这样想可能会有所帮忙,因为“受权”在其余中央被重载了。Spring Security 的架构旨在将身份验证与受权离开,并为两者提供策略和扩大点。
验证
认证的次要策略接口是 AuthenticationManager,它只有一个办法:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
An 能够在其办法 AuthenticationManager 中做三件事之一:authenticate()
如果它能够验证输出代表一个无效的主体,则返回一个 Authentication(通常带有)。authenticated=true
AuthenticationException 如果它认为输出代表有效的委托人,则抛出一个。
null 如果它不能决定返回。
AuthenticationException 是运行时异样。它通常由应用程序以通用形式解决,具体取决于应用程序的款式或用处。换句话说,通常不冀望用户代码来捕捉和解决它。例如,Web UI 可能会出现一个阐明身份验证失败的页面,并且后端 HTTP 服务可能会发送一个 401 响应,WWW-Authenticate 依据上下文是否有标头。
最罕用的实现 AuthenticationManager 是 ProviderManager,它委托给一个 AuthenticationProvider 实例链。AnAuthenticationProvider 有点像 an AuthenticationManager,但它有一个额定的办法容许调用者查问它是否反对给定的 Authentication 类型:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
办法中的 Class<?> 参数 supports()是真的 Class<? extends Authentication>(只询问它是否反对传递给 authenticate()办法的货色)。AProviderManager 能够通过委托给 AuthenticationProviders. 如果 aProviderManager 不能辨认特定的 Authentication 实例类型,则会跳过它。
AProviderManager 有一个可选的父级,如果所有提供者都返回,它能够征询它 null。如果父级不可用,则 null Authentication 后果为 AuthenticationException.
有时,应用程序具备受爱护资源的逻辑组(例如,与门路模式匹配的所有 Web 资源,例如 /api/**),并且每个组都能够有本人的专用 AuthenticationManager. 通常,它们中的每一个都是一个 ProviderManager,并且它们共享一个父级。而后,父级是一种“全局”资源,充当所有提供者的后备。
「Spring」认证平安架构指南
图 1. 应用的 AuthenticationManager 层次结构 ProviderManager
自定义身份验证管理器
Spring Security 提供了一些配置助手来疾速获取应用程序中设置的常见身份验证管理器性能。最罕用的帮忙程序是
AuthenticationManagerBuilder,它非常适合设置内存、JDBC 或 LDAP 用户详细信息或增加自定义 UserDetailsService. 以下示例显示了一个配置全局(父)的应用程序 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 应用程序安全性的更多详细信息,请参阅 Web 安全性)。请留神,它
AuthenticationManagerBuilder 是 @Autowired 在 a 中的一个办法中 @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 在配置器中应用了 of 办法,那么
AuthenticationManagerBuilder 将仅用于构建“本地”AuthenticationManager,这将是全局办法的子对象。在 Spring Boot 应用程序中,您能够 @Autowired 将全局 bean 放入另一个 bean,但您不能对本地 bean 执行此操作,除非您本人显式公开它。
Spring Boot 提供了一个默认的全局 AuthenticationManager(只有一个用户),除非您通过提供本人的 bean 类型来抢占它 AuthenticationManager。默认值自身就足够平安,您不用放心太多,除非您被动须要自定义全局 AuthenticationManager. 如果您进行任何构建 AuthenticationManager.
受权或访问控制
一旦认证胜利,咱们就能够持续进行受权,这里的外围策略是 AccessDecisionManager. 框架提供了三个实现,所有三个都委托给一个 AccessDecisionVoter 实例链,有点像 ProviderManager 委托给 AuthenticationProviders.
AnAccessDecisionVoter 思考一个 Authentication(代表一个主体)和一个平安的 Object,它被装璜了 ConfigAttributes:
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
在和的 Object 签名中是齐全通用的。它代表用户可能想要拜访的任何内容(Web 资源或 Java 类中的办法是最常见的两种状况)。它们也是相当通用的,代表平安的装璜,带有一些决定拜访它所需的权限级别的元数据。是一个接口。它只有一个办法(十分通用并返回 a),因而这些字符串以某种形式编码了资源所有者的用意,表白了对于容许谁拜访它的规定。典型的是用户角色的名称(如 or),它们通常具备非凡格局(如
AccessDecisionManagerAccessDecisionVoterConfigAttributesObjectConfigAttributeStringConfigAttributeROLE_ADMINROLE_AUDITROLE_前缀)或示意须要评估的表达式。
大多数人应用默认值 AccessDecisionManager,即 AffirmativeBased(如果任何选民必定返回,则授予拜访权限)。通过增加新的或批改现有的工作形式,任何定制都偏向于产生在选民身上。
应用 Spring 表达式语言 (SpEL) 表达式十分常见 ConfigAttributes——例如,isFullyAuthenticated() && hasRole(‘user’). 这由 AccessDecisionVoter 能够解决表达式并为它们创立上下文的 an 反对。要扩大能够解决的表达式范畴,SecurityExpressionRoot 有时还须要自定义实现 SecurityExpressionHandler.
网络安全
Web 层中的 Spring Security(用于 UI 和 HTTP 后端)是基于 Servlet 的 Filters,所以首先看一下 Filters 个别的作用是有帮忙的。下图显示了单个 HTTP 申请的处理程序的典型分层。
「Spring」认证平安架构指南
客户端向应用程序发送申请,容器依据申请 URI 的门路决定利用哪些过滤器和哪个 servlet。最多一个 servlet 能够解决一个申请,然而过滤器造成一个链,所以它们是有序的。事实上,如果过滤器想要本人解决申请,它能够否决链的其余部分。过滤器还能够批改上游过滤器和 servlet 中应用的申请或响应。过滤器链的程序十分重要,Spring Boot 通过两种机制来治理它:@Beans 类型 Filter 能够有一个 @Order 或实现 Ordered,它们能够是一个 FilterRegistrationBean 它自身有一个订单作为其 API 的一部分。一些现成的过滤器定义了本人的常量来帮忙表明他们喜爱的绝对于彼此的程序(例如,SessionRepositoryFilter 来自 Spring Session 有一个 DEFAULT_ORDERof Integer.MIN_VALUE + 50,它通知咱们它喜爱在链中处于晚期,然而它不排除在它之前呈现其余过滤器)。
Spring Security 在链中作为单个装置 Filter,其具体类型是 FilterChainProxy,起因咱们很快就会介绍。在 Spring Boot 应用程序中,平安过滤器位于 @Bean 中 ApplicationContext,默认状况下会装置它,以便将其利用于每个申请。它装置在由 定义的地位
SecurityProperties.DEFAULT_FILTER_ORDER,该地位又被锚定
FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER(Spring Boot 应用程序在包装申请期间望过滤器具备的最大程序,批改其行为)。不仅如此:从容器的角度来看,Spring Security 是一个繁多的过滤器,但在其中,还有额定的过滤器,每个过滤器都扮演着非凡的角色。下图显示了这种关系:
「Spring」认证平安架构指南
图 2. Spring Security 是繁多物理的 Filter,但将解决委托给外部过滤器链
实际上,平安过滤器中甚至还有一层间接性:它通常以 . 的模式装置在容器中 DelegatingFilterProxy,而不用肯定是 Spring @Bean。代理委托给 a FilterChainProxy,它始终是 a @Bean,通常具备固定名称 springSecurityFilterChain。它 FilterChainProxy 蕴含在外部排列为过滤器链(或链)的所有平安逻辑。所有过滤器都具备雷同的 API(它们都实现了 FilterServlet 标准中的接口),并且它们都有机会否决链的其余部分。
能够有多个过滤器链都由 Spring Security 在同一顶层治理,FilterChainProxy 并且对容器都是未知的。Spring Security 过滤器蕴含一个过滤器链列表,并将申请分派到与其匹配的第一个链。下图显示了基于匹配申请门路(/foo/匹配之前 /)产生的调度。这很常见,但不是匹配申请的惟一办法。这个分派过程最重要的特点是只有一个链解决一个申请。
「Spring」认证平安架构指南
图 3. Spring SecurityFilterChainProxy 将申请分派到匹配的第一个链。
没有自定义平安配置的一般 Spring Boot 应用程序有几个(称为 n)过滤器链,其中通常 n=6。第一个 (n-1) 个链只是为了疏忽动态资源模式,例如 /css/and/images/和谬误视图:/error.(门路能够由用户应用 security.ignored 配置 SecurityPropertiesbean 管制。)最初一个链匹配无所不包的门路 (/**) 并且更加沉闷,蕴含身份验证、受权、异样解决、会话解决、标头写入等逻辑上。默认状况下,该链中共有 11 个过滤器,但通常用户无需关怀应用哪些过滤器以及何时应用。
容器不晓得 Spring Security 外部的所有过滤器这一事实很重要,尤其是在 Spring Boot 应用程序中,默认状况下,所有 @Beans 类型 Filter 都主动注册到容器中。因而,如果您想将自定义过滤器增加到平安链中,则须要不将其设为 a@Bean 或将其包装在 FilterRegistrationBean 明确禁用容器注册的 a 中。
创立和自定义过滤器链
Spring Boot 应用程序(具备申请匹配器的应用程序)中的默认后备过滤器链 /** 具备预约义的
SecurityProperties.BASIC_AUTH_ORDER. 您能够通过设置将其齐全敞开 security.basic.enabled=false,也能够将其用作后备并以较低的程序定义其余规定。要执行后者,请增加一个(或)@Bean 类型并用 装璜类,如下所示:
WebSecurityConfigurerAdapterWebSecurityConfigurer@Order
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER – 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/match1/**")
...;
}
}
这个 bean 导致 Spring Security 增加一个新的过滤器链并在回退之前对其进行排序。
与另一组相比,许多应用程序对一组资源的拜访规定齐全不同。例如,托管 UI 和反对 API 的应用程序可能反对基于 cookie 的身份验证,通过重定向到 UI 局部的登录页面和基于令牌的身份验证,以及对 API 局部的未经身份验证申请的 401 响应。每组资源都有本人
WebSecurityConfigurerAdapter 的惟一程序和本人的申请匹配器。如果匹配规定重叠,则最早排序的过滤器链获胜。
申请匹配调度和受权
平安过滤器链(或等效的 a
WebSecurityConfigurerAdapter)有一个申请匹配器,用于决定是否将其利用于 HTTP 申请。一旦决定利用特定的过滤器链,就不会利用其余过滤器链。然而,在过滤器链中,您能够通过在配置器中设置额定的匹配器来对受权进行更细粒度的管制 HttpSecurity,如下所示:
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER – 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/match1/**")
.authorizeRequests()
.antMatchers("/match1/user").hasRole("USER")
.antMatchers("/match1/spam").hasRole("SPAM")
.anyRequest().isAuthenticated();
}
}
配置 Spring Security 时最容易犯的谬误之一就是遗记这些匹配器实用于不同的过程。一种是整个过滤器链的申请匹配器,另一种只是抉择要利用的拜访规定。
将应用程序平安规定与执行器规定相结合
如果您将 Spring Boot Actuator 用于治理端点,您可能心愿它们是平安的,并且默认状况下它们是平安的。事实上,只有将执行器增加到平安应用程序,您就会取得一个仅实用于执行器端点的附加过滤器链。它是应用仅匹配执行器端点的申请匹配器定义的,它的程序为
ManagementServerProperties.BASIC_AUTH_ORDER,比默认的 SecurityProperties 回退过滤器少 5 个,因而在回退之前对其进行查问。
如果您心愿您的应用程序平安规定利用于执行器端点,您能够增加一个比执行器更早排序的过滤器链,并且该过滤器链具备蕴含所有执行器端点的申请匹配器。如果您更喜爱执行器端点的默认平安设置,最简略的办法是在执行器之后增加您本人的过滤器,但在回退之前增加(例如,
ManagementServerProperties.BASIC_AUTH_ORDER + 1),如下所示:
@Configuration
@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...;
}
}
Web 层中的 Spring Security 以后与 Servlet API 相关联,因而它仅在 servlet 容器中运行应用程序时才真正实用,无论是嵌入的还是其余的。然而,它不依赖于 Spring MVC 或 Spring Web 堆栈的其余部分,因而它能够在任何 servlet 应用程序中应用——例如,应用 JAX-RS 的应用程序。
办法平安
除了反对爱护 Web 应用程序之外,Spring Security 还反对将拜访规定利用于 Java 办法执行。对于 Spring Security,这只是一种不同类型的“受爱护资源”。对于用户,这意味着应用雷同格局的 ConfigAttribute 字符串(例如,角色或表达式)申明拜访规定,但在代码中的不同地位。第一步是启用办法安全性——例如,在咱们应用程序的顶层配置中:
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}
而后咱们能够间接装璜办法资源:
@Service
public class MyService {
@Secured(“ROLE_USER”)
public String secure() {
return "Hello Security";
}
}
此示例是具备平安办法的服务。如果 Spring 创立了 @Bean 这种类型的 a,它会被代理,调用者必须在办法理论执行之前通过平安拦截器。如果拜访被回绝,调用者会失去一个 AccessDeniedException 而不是理论的办法后果。
您能够在办法上应用其余正文来强制施行平安束缚,特地是 @PreAuthorize 和 @PostAuthorize,它们容许您编写别离蕴含对办法参数和返回值的援用的表达式。
将 Web 安全性和办法安全性联合起来并不少见。过滤器链提供用户体验性能,例如身份验证和重定向到登录页面等,办法安全性提供更细粒度的爱护。
应用线程
Spring Security 基本上是线程绑定的,因为它须要使以后通过身份验证的主体可用于各种上游消费者。根本构建块是 SecurityContext,它可能蕴含一个 Authentication(当用户登录时,它是一个 Authentication 显式的 authenticated)。您始终能够 SecurityContext 通过动态便捷办法拜访和 SecurityContextHolder 操作 ThreadLocal. 以下示例显示了这种安顿:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);
用户利用程序代码执行此操作并不常见,但如果您须要编写自定义身份验证过滤器(只管即便这样,Spring Security 中也有一些基类可供您应用,以便您能够防止须要应用 SecurityContextHolder)。
如果您须要拜访 Web 端点中以后通过身份验证的用户,能够在 a 中应用办法参数 @RequestMapping,如下所示:
@RequestMapping(“/foo”)
public String foo(@AuthenticationPrincipal User user) {
… // do stuff with user
}
此注解将电流 Authentication 拉出 SecurityContext 并调用其上的 getPrincipal()办法以产生办法参数。Principalin an 的类型 Authentication 取决于 AuthenticationManager 用于验证身份验证的类型,因而这可能是一个有用的小技巧,能够获取对用户数据的类型平安援用。
如果应用 Spring Security,则 PrincipalfromHttpServletRequest 是 type Authentication,所以你也能够间接应用它:
@RequestMapping(“/foo”)
public String foo(Principal principal) {
Authentication authentication = (Authentication) principal;
User = (User) authentication.getPrincipal();
… // do stuff with user
}
如果您须要编写在不应用 Spring Security 时工作的代码,这有时会很有用(您须要在加载 Authentication 类时更加进攻)。
异步解决平安办法
因为 SecurityContext 是线程绑定的,如果您想要执行任何调用平安办法的后盾解决(例如,with @Async),您须要确保流传上下文。这归结为应用在后盾执行 SecurityContext 的工作(Runnable、等)包装。CallableSpring Security 提供了一些帮忙程序来简化此操作,例如 和 的包装 Runnable 器 Callable。要流传 SecurityContextto@Async 办法,您须要提供 AsyncConfigurer 并确保其 Executor 类型正确:
@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
}
}
文末备注:
Spring Security Architecture 起源:Spring 中国教育管理中心