解锁Spring Security6:核心安全机制

结合 Spring Security 官网,简单和小伙伴们聊一聊 Spring Security 架构。

应用程序安全可以归结为两个问题:

  1. 认证(你是谁?)
  2. 授权(你被允许做什么?)

Spring Security 设计了一个架构,旨在将认证与授权分离,并为两者都提供了策略和扩展点。

一 认证

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

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

一个 AuthenticationManager 在它的 authenticate() 方法中可以做三件事:

  • 如果它能验证输入表示一个有效的用户,则返回一个Authentication(通常带有authenticated=true)。
  • 如果它认为输入表示一个无效的用户,则抛出一个AuthenticationException
  • 如果它无法决定,则返回 null

AuthenticationManager 最常用的实现是 ProviderManager,它委托给一系列 AuthenticationProvider 实例。

一个 AuthenticationProvider 有点像一个 AuthenticationManager,但它有一个额外的方法,允许调用者查询它是否支持给定的 Authentication 类型:

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

boolean supports(Class<?> authentication);
}

supports() 方法中的 Class<?> 参数实际上是 Class<? extends Authentication>,一个 ProviderManager 可以通过委托给一系列 AuthenticationProviders 来支持同一应用程序中的多种不同的认证机制。如果 ProviderManager 不认识特定的 Authentication 实例类型,它就会被跳过。

ProviderManager 有一个可选的父级,如果所有提供者都返回 null,那么认证操作会委托给父级去完成,父级也可以有父级,父级如果不存在或者认证失败,那么最终就认证失败。

下面这张图展示了 ProviderManagerAuthenticationManager的层次结构:

img

如上图,应用程序有一组受保护的资源,例如 /api/**,每个组都可以有自己专用的 AuthenticationManager。通常情况下,每个组都是一个 ProviderManager,并且它们共享一个父级。然后,父级就是一种“全局”资源,作为所有提供者的后备。

当然,接下来是第二部分的翻译:

二 安全性

Spring Security 在 Web 层级(用于用户界面和 HTTP 后端)基于 Servlet Filters,因此首先了解 Filters 的角色会有所帮助。下图展示了单个 HTTP 请求的处理器典型层级结构。

img

客户端向应用程序发送请求,容器根据请求 URI 的路径决定应用哪些过滤器和 Servlet。一个 Servlet 可以处理一个请求,但过滤器形成链,因此它们是有序的。

实际上,如果过滤器想要处理请求,它可以否决链中剩余的部分(提前终止)。过滤器还可以修改下游过滤器。过滤器链的顺序非常重要,Spring Boot 通过两种机制管理它:

  1. @Beans 类型为 Filter 可以有 @Order 注解或实现 Ordered 接口,并且它们可以是 FilterRegistrationBean 的一部分,后者本身在其 API 中具有顺序。
  2. 一些默认的过滤器定义了它们自己的顺序常量,例如,来自 Spring Session 的 SessionRepositoryFilter 有一个 DEFAULT_ORDERInteger.MIN_VALUE + 50,这告诉我们它喜欢在链中早期出现,但它不排除其他过滤器在它之前出现。

Spring Security 作为链中的单个 Filter 安装,其具体类型是 FilterChainProxy。在 Spring Boot 应用程序中,安全过滤器是 ApplicationContext 中的 @Bean,默认情况下会被自动加载,以便应用于每个请求。它加载在由 SecurityProperties.DEFAULT_FILTER_ORDER 定义的位置,该位置又由 FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER 锚定。

从 Tomcat 容器的角度来看,Spring Security 是一个单独的过滤器,但在 Spring Security 内部,还有额外的过滤器,每个过滤器都扮演着特殊的角色。下图展示了这种关系:

img

实际上,在安全过滤器中还有一层间接性:它通常作为 DelegatingFilterProxy 加载在 Tomcat 容器中,它不必是 Spring @BeanDelegatingFilterProxy 会把接收到的请求委派给一个 FilterChainProxy

可以有多个由 Spring Security 在同一个顶级 FilterChainProxy 中管理的过滤器链,所有这些都对容器未知。Spring Security 过滤器包含了一个过滤器链列表,并将请求分派给第一个匹配的链。下图显示了基于请求路径匹配(/foo/**/** 之前匹配)的分派。这是非常常见的,但不是匹配请求的唯一方式,分派过程最重要的特性是,只有一个链曾经处理过请求。

img

例如手机 App 登录可以不用考虑 CSRF 跨站请求伪造,但是 Web 端登录则要考虑,跨站请求伪造由 CsrfFilter 负责防御,那么我们就可以利用这个能力,对来自手机的请求 /phone/** 和来自 Web 的请求 /web/** 分别用不同的过滤器链处理。

注意:Spring Security 内部的所有过滤器对容器都是不可见的,这很重要,特别是在 Spring Boot 应用程序中,因为在 Spring Boot 中,默认情况下,所有类型为 Filter@Bean 都会自动注册到容器中。因此,如果您想将自定义过滤器添加到安全链中,您需要要么不让它成为 @Bean,要么将其包装在 FilterRegistrationBean 中。

Spring Security 博大精深,如果小伙伴们想要彻底掌握 Spring Security,那么不妨看看我最近新出的一套 Spring Security 视频教程。

最新版 Spring Security + OAuth2 视频杀青啦!

不废话,先来看目录:

基本上把 Spring Security 的方方面面以及 OAuth2 都讲到了。最重要的是,贴合了很多小伙伴们日常常见的开发场景,比如短信验证码登录,微信 OAuth2 登录等等都有讲到。

这套视频是基于目前最新版的 Spring Security6 录制的,Spring Security6 在 API 层面的变化还是蛮大的,引入了大量的 Lambda 表达式去简化配置,很多旧版的写法在 Spring Security6 中被废弃,并在将来在 Spring Security7 中会移除相关的 API,所以说最近的 Spring Security 更新还是蛮激进的。

另一方面就是这套视频包含了全新的基于目前最新版 Spring Security 录制的 OAuth2 教程,松哥在 2020 年的时候出过图文版的 OAuth2 教程,但是,现在新版的 OAuth2 也有很多变化,不仅仅是 API 层面的变化,授权模式也发生了一些变化,传统的密码模式、简化模式现在都不再支持,转而引入了 PKCE 模式,并且利用 OIDC 简化用户信息获取。同时,在 2020 年还处于萌芽状态的 Spring Security OAuth2 Server 这个项目,目前也趋于成熟,也可以直接使用了,这些松哥都在视频中和大家做了详细介绍。在 OAuth2 环节我也和大家分享了如何使用微信的 OAuth2 登录。

总之这一套教程,让大家彻底理解系统的安全管理。

之前有小伙伴说我直接自己写过滤器,既灵活还简单。我并不反对这种做法,但是有一个前提就是你很牛,你自己写的过滤器有考虑到计时攻击,有考虑到 XSS 攻击,有考虑到点击劫持,有考虑跨站请求伪造。。。等等太多了。当然,这些问题如果你都没有考虑到,那么 Spring Security 都有帮你考虑到并提供解决方案!

理解了 Spring Security,再去看市面上其他的安全管理框架,都会豁然开朗。

这套教程目前给小伙伴们提供了一个试看视频:

这套视频是付费的,¥499,有需要的小伙伴加微信备注 499,五一假期正好操练起来。