Spring Security是Spring家族中历史比较悠久的框架之一,具备完整而强大的功能体系。对于日常开发过程中常见的单体应用、微服务架构,以及响应式系统,Spring Security都能够进行无缝集成和整合,并提供多种常见的安全性功能。
本章作为全书的开篇,将对Spring Security的功能体系作简要介绍,并引出开发人员在使用该框架时所涉及的配置体系。本章的末尾还给出了全书的内容架构。
一般而言,日常开发过程中涉及的业务系统或多或少都会有安全性相关的技术需求,其中最常见的就是用户认证和访问授权。设想一下,如果一个应用程序在认证和授权机制上存在漏洞,将可能泄露用户信息等敏感数据,给用户和公司造成巨大的损失,这样的系统肯定是无法面向生产的。
同时,人们也应该认识到,虽然认证和授权的概念比较简单,但要从零开始构建这些功能,并且做到没有安全漏洞并不是一件容易的事情。这时候需要引入专业的安全性开发框架。在Java 领域中,Spring Security 就是一个应用非常广泛的安全性开发框架,也是 Spring 家族中历史比较悠久的一个框架。Spring Security在日常开发过程中不仅可以与Spring Boot等框架无缝集成,而且是Spring Cloud等综合性开发框架的底层基础框架之一。Spring Security的功能完备且强大。
可以说,如果没有Spring Security,Spring自身的安全性也将无法得到保障。任何使用Spring的地方,都会通过Spring Security对应用程序进行保护。作为开发人员,在日常开发过程中需要用到Spring Security的场景非常多。事实上,对Web应用程序而言,除了分布式环境下的认证和授权漏洞,常见的安全性问题还包括跨站点脚本攻击、跨站点请求伪造、敏感数据暴露、缺乏方法级访问控制等。针对这些安全性问题,开发人员都需要全面设计并实现对应的安全性功能,而Spring Security已经为开发人员提供了相应的解决方案,主要包括如下内容。
在普遍倡导用户隐私和数据价值的当下,掌握各种安全性相关技术已经成为开发人员必须具备的能力。
在Spring Boot出现之前,Spring Security就已经存在多年。但Spring Security的发展一直都不是很顺利,主要问题在于在应用程序中集成和配置Spring Security框架的过程比较复杂。随着Spring Boot的兴起,基于Spring Boot提供的针对Spring Security的自动配置方案,开发人员可以零配置使用Spring Security。在Spring Boot应用程序中使用Spring Security,只须在Maven工程的pom文件中添加如下依赖即可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>
接下来我们构建一个简单的HTTP端点。
@RestController
public
class
DemoController { @GetMapping("/hello")
public
String hello() {
return
"Hello World!"; }}
现在,启动该Spring Boot应用程序,通过浏览器访问“http://localhost:8080//hello”端点。大家可能希望得到“Hello World!”这个输出结果,但事实上,浏览器会跳转到图1-1所示的Spring Security内置的登录界面。
图1-1 Spring Security内置的登录界面
那么,为什么会弹出该登录界面呢?原因就在于人们添加spring-boot-starter-security依赖之后,Spring Security为应用程序自动嵌入用户认证机制。
接下来我们将围绕该登录场景分析如何获取登录所需的用户名和密码。可以注意到Spring Boot的控制台启动日志中出现了如下日志:
Using generated security password: 707d7469-631f-4d92-ab71-3809620fe0dc
这行日志就是Spring Security生成的,用于表示创建了一个密码,而用户名则是系统默认的“user”。输入正确的用户名和密码,浏览器就会输出“Hello World!”这个结果。
上述过程演示的是Spring Security提供的认证功能,而其只是Spring Security众多功能中的一项基础功能。Spring Security提供的是一套完整的安全性解决方案。面向不同的业务需求和应用场景,Spring Security分别实现了对应的安全性功能。接下来我们将从单体应用、微服务架构及响应式系统三个维度对这些功能展开讨论。
在软件系统中,开发人员把需要访问的内容定义为资源(resource),而安全性设计的核心目标就是对这些资源进行保护,确保对它们的访问是安全受控的。例如,在Web应用程序中,对外暴露的HTTP端点就可以被理解为资源。针对资源的安全性访问,业界也存在一些常见的技术体系。在讲解这些技术体系之前,先来了解安全领域中常见但又容易混淆的两个概念——认证(authentication)和授权(authorization)。
人们首先需要明确“认证”的概念。所谓“认证”,解决的是“你是谁”这一问题。也就是说,对于每次访问请求,系统都能判断出访问者是否具有合法的身份标识。
一旦明确“你是谁”,下一步就可以判断“你能做什么”,这个步骤就是“授权”。通用的授权模型是基于权限管理体系的,也就是说,授权是对资源、权限、角色和用户的一种组合处理。
如果将认证和授权结合起来,就构成了对系统中资源进行安全性管理的常见的一种解决方案,即首先判断资源访问者的有效身份,然后确定其是否有访问这个资源的合法权限,如图1-2所示。
图1-2 基于认证和授权机制的资源访问安全性
图1-2所示为一种通用解决方案。在不同的应用场景及技术体系下该解决方案可以衍生出不同的实现策略。Web应用系统中的认证和授权模型与图1-2中的类似,但在具体设计和实现过程中也有其特殊性。
认证的需求相对比较明确。人们需要构建一套完善的存储体系来保存和维护用户信息,并确保这些用户信息在处理请求的过程中能够得到合理利用。
而对于授权,情况可能会比较复杂。对某个特定的Web应用程序而言,人们面临的第一个问题是判断一个HTTP请求是否具备访问自己的权限。或更进一步,即使该请求具备访问Web应用程序的权限,但并不意味着请求能够访问Web应用程序所暴露的所有HTTP端点。对于某些核心功能,需要具备较高的权限才能访问,而有些功能则不需要。这就是人们需要解决的第二个问题,即如何对访问的权限进行精细化管理,如图1-3所示。
图1-3 Web应用程序访问授权
在图1-3中,假设请求具备访问Web应用程序中端点2、3、4的权限,但不具备访问端点1的权限。想要达到这种效果,一般的做法是引入角色体系,即对不同的用户设置不同等级的角色,角色等级不同对应的访问权限也不同,而每个请求都可以绑定到某个或多个角色。
接下来,把认证和授权结合起来,就可以梳理出Web应用程序访问场景下的安全性实现方案,如图1-4所示。
图1-4 Web应用程序访问场景下的认证和授权整合
可以看到,用户先通过请求传递用户身份凭证并完成用户认证,然后根据该用户所具备的用户角色来获取访问权限,并最终完成对HTTP端点的访问授权。
围绕认证和授权,还需要一系列的额外功能才能确保整个流程得以实现。这些功能包括用于密码保护的加解密机制、用于实现方法级的安全访问策略及支持跨域等。这些功能在本书中都会一一展开讨论。
对微服务架构而言,情况比Web应用程序复杂很多,因为其涉及服务与服务之间的调用关系。这里继续沿用“资源”这个概念,对应到微服务系统中,服务提供者所充当的角色就是资源服务器,而服务消费者就是客户端。所以,各个服务本身既可以是客户端,也可以是资源服务器,或者两者兼之。
接下来我们把认证和授权结合起来,可以梳理出微服务访问场景下的安全性实现方案,如图1-5所示。
可以看到,与Web应用程序相比,在微服务架构中,由于开发人员需要把认证和授权的过程进行集中化管理,所以在图1-5中出现了一个授权中心。授权中心首先会获取客户端请求中的身份凭证信息,然后基于该身份凭证信息生成一个令牌(Token),该令牌包含访问权限范围和有效期。
客户端获取令牌之后就可以基于该令牌发起对微服务的访问。这时资源服务器需要对该令牌进行认证,并根据令牌的权限范围和有效期从授权中心获取该请求所能访问的特定资源。在微服务系统中,对外的资源表现形式同样可以理解为一个个HTTP端点。
图1-5 微服务访问场景下的认证和授权整合
图1-5中的关键点在于构建用于生成和验证令牌的授权中心,为此需要引入OAuth2协议。OAuth2协议在客户端和资源服务器之间设置了一个授权层,并确保令牌能够在各个微服务之间有效传递,如图1-6所示。
图1-6 OAuth2协议在微服务访问场景中的应用
OAuth2是一个相对复杂的协议,综合应用摘要认证、签名认证、HTTPS等安全性手段,需要提供令牌生成、校验及公(私)钥管理等功能,同时也需要开发者入驻并进行权限粒度控制。人们一般不会自己去实现如此复杂的协议,而是倾向于借助特定工具以避免重复“造轮子”。Spring Security为人们提供了实现这一协议的完整解决方案。该解决方案完成适用于微服务系统的认证和授权机制。
随着Spring 5的发布,我们迎来了响应式编程(reactive programming)的全新发展时期。响应式编程是Spring 5核心的新功能,也是Spring家族目前重点推广的技术体系之一。Spring 5中的响应式编程模型以Project Reactor库为基础,而后者则实现了响应式流规范。事实上,Spring Boot从2.x版本开始也全面依赖于Spring 5。同样,在Spring Security中,用户账户体系的建立、用户认证和授权、方法级别的安全访问、OAuth2协议等传统开发模式下所具备的安全性功能也都具备对应的响应式版本。
在介绍Spring Security各项功能的具体使用方法之前,我们有必要进一步了解它的配置体系。在Spring Security中,关于认证和授权等基础功能的实现都依赖开发人员如何合理利用和扩展其配置体系。那么,Spring Security为什么需要构建这种配置体系呢?主要在于针对认证和授权等功能,通常都存在不止一种的实现方式。
例如,针对用户账户存储这个切入点,就可以设计出多种不同的策略。作为一种轻量级的实现方式,人们可以把用户名和密码保存在内存中。更常见的方式是,把这些认证信息存储在关系型数据库中。当然,如果使用了轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP),那么文件系统也是一种不错的存储媒介。显然,针对这些可选的实现方式,需要为开发人员提供一种机制以便根据自身的需求灵活设置,这就是配置体系的作用。
同时,读者可能已注意到,在前面的示例中,我们并没有进行任何配置也能让Spring Security发挥作用,这说明框架内部的功能采用默认配置。就用户认证这一场景而言,Spring Security初始化了一个默认的用户名“user”,并在应用程序启动时自动生成密码。当然,通过这种方式自动生成的密码在每次启动应用时都会变化,不适合面向正式的应用。
通过查看框架的源码我们可以进一步理解 Spring Security 中的一些默认配置。在 Spring Security中,初始化用户信息所依赖的配置类是WebSecurityConfigurer接口,该接口实际上是一个空接口,它继承了更为基础的SecurityConfigurer接口。在日常开发过程中,开发人员通常不需要自己实现该接口,而是使用WebSecurityConfigurerAdapter配置适配器类来简化该配置类的使用方式。WebSecurityConfigurer Adapter的configure()方法如下所示。
protected
void
configure(HttpSecurity http)
throws
Exception { http .authorizeRequests() .anyRequest() .authenticated() .and() .formLogin() .and() .httpBasic();}
上述代码就是Spring Security中作用于用户认证和访问授权的默认实现。这里用到多个常见的配置方法。根据1.2节的介绍,一旦在代码类路径中引入Spring Security框架,访问任何端点时都会弹出一个登录界面以完成用户认证。认证是授权的前置流程,认证结束之后进入授权环节。结合这些配置方法的名称,接下来我们简单分析一下这种默认效果的实现过程。
首先,通过HttpSecurity类的authorizeRequests()方法对所有访问HTTP端点的HttpServletRequest进行限制。其次,anyRequest().authenticated()语句指定对所有请求都执行认证,也就是说,没有通过认证的用户无法访问任何端点。再次,formLogin()语句指定使用表单登录方式进行认证,此时会弹出一个登录界面。最后,httpBasic()语句表示可以使用HTTP基础认证(basic authentication)方法来完成认证。
在日常开发过程中,开发人员可以继承WebSecurityConfigurerAdapter类并且覆写上述的configure()方法来完成配置工作。而Spring Security拥有一批类似于WebSecurityConfigurerAdapter的配置适配器类。
配置体系是开发人员使用Spring Security框架的主要手段之一。关于配置体系的讨论将贯穿全书。我们将见识到Spring Security提供的全面而灵活的配置功能。
图1-7归纳了本书内容的架构。第1篇(第1章)引入了 Spring Security 这个主流的安全性开发框架。而剩下的其他章节将按照认证和授权→扩展插件→微服务安全→响应式安全的主线来展开内容,呈递进关系。
图1-7 全书内容架构
第2篇(第2章~第5章)将介绍Spring Security的一些基础性功能,包括认证、授权和加密等。这些功能是Spring Security框架的入口,也是框架的其他功能的依赖。该篇不仅仅会介绍它们的使用方法,而且会进一步讨论其原理。
第3篇(第6章~第9章)介绍的功能面向特定需求,可以用于构建比较复杂的应用场景,包括过滤器、CSRF保护、跨域CORS,以及针对非Web应用程序的全局方法安全机制。
第4篇(第10章~第14章)注重介绍微服务开发框架Spring Cloud与 Spring Security 之间的整合,同时对OAuth2协议和JWT进行了全面介绍,并使用这些技术体系构建了安全的微服务系统,以及单点登录系统。
第5篇(第15章和第16章)介绍Spring Security框架在应用上的一些扩展内容,包括如何在Spring Security中引入全新的响应式编程技术,以及如何对应用程序安全性进行测试。
本章通过一个简单案例引入Spring Security框架,并基于日常开发的安全需求剖析了Spring Security框架所具备的功能体系。不同的功能对应不同的应用场景,在普通的Web应用程序、微服务架构、响应式系统中都可以使用Spring Security框架所提供的功能以确保系统的安全性。同时,Spring Security中关于认证、授权等核心功能的实现都是通过配置体系来定制化开发和管理的,本章也对Spring Security所具备的配置体系做了展开介绍。