跳转至

安全现状#

约 7614 个字 4 张图片 预计阅读时间 152 分钟

本章内容包括

  • 介绍什么是 Spring Security 以及它能为你解决哪些问题
  • 软件应用的安全性意味着什么
  • 为什么软件安全至关重要,以及你为什么需要关注这一点

开发者们越来越意识到软件安全 的重要性,并且从软件开发的初期就开始主动承担起安全方面的责任。通常,作为开发者,我们首先了解到,应用程序的目标是为了解决业务问题。这个目标意味着数据会以某种方式被处理、存储,并最终按照需求展示给用户。这种对软件开发的整体认知,从我们刚开始学习相关技术时就被灌输,但遗憾的是,这种认知往往忽略了开发过程中同样重要的一些实践。虽然从用户的角度来看,应用程序能够正常运行,并且在功能上实现了用户的预期,但在最终的结果背后,仍然隐藏着许多不为人知的细节。

诸如性能、可扩展性、可用性和安全性等非功能性软件质量,以及其他相关因素,都会在不同时间段内产生影响,从短期到长期不等。如果在早期没有充分考虑这些质量属性,可能会极大地影响应用所有者的盈利能力(见图1.1)。此外,忽视这些因素还可能引发其他系统的故障(例如,无意中参与到分布式拒绝服务[DDoS] 攻击中)。非功能性需求的隐蔽性(即很难察觉某些内容是否缺失或不完整)使得这些问题更加危险。


图1.1 用户通常关注系统应该具备哪些功能——也就是系统的功能性方面。偶尔,他们可能会考虑系统性能这一非功能性属性,但很少有人会注意到安全措施。相比于直接与功能相关的需求,那些非功能性的需求往往容易被忽视。

在开发软件系统时,需要考虑多个非功能性方面。实际上,这些方面都非常重要,在软件开发过程中都应当被认真对待。本书将重点关注其中之一:安全性。你将学习如何一步步地使用 Spring Security 来保护你的应用程序。

本章将为你梳理与安全相关的核心概念。在接下来的内容中,我们会结合实际案例进行讲解,必要时我会回顾本章的相关描述,并在合适的地方补充更多细节。此外,你还会在书中看到针对特定主题的参考资料(包括书籍、文章和文档),便于你进一步深入学习。

探索Spring Security#

本节将探讨 Spring Security 与 Spring 之间的关系。首先,在开始使用它们之前,了解两者之间的联系非常重要。如果我们查阅官方网站(https://spring.io/projects/spring-security ),可以看到 Spring Security 被描述为一个功能强大且高度可定制的身份认证与访问控制框架。简单来说,Spring Security 就是一个能够极大简化为 Spring 应用集成安全机制的框架。

Spring Security 是在 Spring 应用中实现应用级安全性的首选方案。通常来说,它的目标是为你提供一种高度可定制的方式,用于实现身份认证、授权以及防护常见攻击。Spring Security 是一款开源软件,采用 Apache 2.0 许可证发布。你可以在 GitHub 上访问它的源代码,地址为 http://mng.bz/vPmJ。我也非常推荐你参与到该项目的贡献中来。

Note

你可以在标准的 Web Servlet、响应式应用以及非 Web 应用中使用 Spring Security。本书将结合最新的 Java 长期支持版本、Spring 和 Spring Boot(即 Java 21、Spring 6 和 Spring Boot 3)来讲解 Spring Security。不过,书中的所有示例同样适用于上一代长期支持版本 Java 17。

我猜你之所以翻开这本书,大概率是因为你在开发 Spring 应用,并且希望为其增加安全保障。Spring Security 很可能是你的最佳选择。它已经成为为 Spring 应用实现应用层安全的事实标准。不过,Spring Security 并不会自动为你的应用提供安全保护,它并不是那种能让应用百毒不侵的万能灵药。开发者需要根据自己应用的需求,了解如何配置和定制 Spring Security。至于怎么做,这取决于很多因素,比如功能需求和系统架构等。

从技术角度来看,在 Spring 应用中集成 Spring Security 实现安全防护其实很简单。你已经有了开发 Spring 应用的经验,因此也清楚这个框架的核心理念就是对 Spring 上下文的管理。你只需要在 Spring 上下文中定义好相关的 bean,框架就会根据你的配置自动进行管理。

你可以通过注解来告诉 Spring 应该做什么,比如暴露接口、为方法添加事务、在切面中拦截方法等等。Spring Security 的配置也是同样的道理,这正是 Spring Security 发挥作用的地方。你希望在定义应用级安全性时,能够像使用 Spring 其他配置一样,灵活地使用注解、Bean,以及整体上符合 Spring 风格的配置方式。在 Spring 应用中,需要保护的行为通常是由方法来定义的。

要理解应用层安全性,可以把它想象成你如何管理自己家的出入权限。你会把钥匙藏在门口的地垫下吗?你的前门甚至有锁吗?同样的道理也适用于应用程序,而 Spring Security 就能帮助你实现这样的功能。它就像一块拼图,提供了多种选择,让你可以拼出最符合自己系统需求的安全方案。你可以选择让你的房子完全不设防,也可以决定并非让所有人都能随意进出你的家门。

你可以像把钥匙藏在门垫下一样简单地配置安全措施,也可以像选择各种报警系统、摄像头和锁具那样复杂。在你的应用程序中,同样有这些选择。但就像现实生活中一样,安全措施越复杂,成本也就越高。在应用程序中,这里的“成本”指的是安全性对可维护性和性能的影响。

那么,如何在 Spring 应用中使用 Spring Security 呢?通常在应用层面,最常见的场景之一就是判断某个人是否有权限执行某个操作或访问某些数据。基于相关配置,你会编写 Spring Security 组件来拦截请求,确保发起请求的人有权限访问受保护的资源。开发者可以根据实际需求灵活配置这些组件,实现精确的权限控制。就像你安装了报警系统,是否要同时为窗户和门都设置好,完全取决于你自己。如果你忘了给窗户装报警器,当有人撬窗而报警器没有反应,这并不是报警系统的错。

Spring Security 组件的其他职责还包括数据存储以及系统各部分之间的数据传输。通过拦截对这些不同部分的调用,这些组件可以对数据进行处理。例如,在数据存储时,这些组件可以应用加密或哈希算法。数据编码确保只有有权限的实体才能访问这些数据。在 Spring 应用中,开发者需要在需要的地方添加并配置相应的组件来完成这部分工作。Spring Security 为我们提供了一个规范,明确了框架要求实现的内容,我们则根据应用的设计来编写具体实现。对于数据传输,同样适用这一原则。

在实际应用中,你会遇到这样一种情况:两个通信组件之间彼此并不信任。那么,第一个组件如何确认第二个组件确实发送了某条特定消息,而不是其他人伪造的?想象一下,你正在和某人通电话,需要向对方提供一些私人信息。你如何确保电话那头确实是有权获取这些数据的合法个人,而不是其他人冒充的?同样的场景也适用于你的应用程序。Spring Security 提供了一系列组件,帮助你以多种方式解决这些问题,但前提是你需要了解应该配置哪些部分,并在系统中正确设置。通过这种方式,Spring Security 能够拦截消息,并在应用程序处理任何发送或接收的数据之前,确保通信的有效性和安全性。

和任何框架一样,Spring 的主要目标之一就是让你用更少的代码实现所需的功能。Spring Security 也是如此。它通过帮助你用更少的代码实现应用中最关键的部分之一—— 安全性,完善了 Spring 作为一个框架的功能。Spring Security 提供了预定义的功能,帮助你避免编写样板代码,或者在不同应用中反复实现相同的逻辑。同时,它也允许你对其各个组件进行配置,因而具备极高的灵活性。简单总结一下:

  • 你可以用 Spring Security 以 Spring 的方式为应用集成应用级安全性。也就是说,你可以使用注解、Bean、Spring 表达式语言(SpEL)等方式。
  • Spring Security 是一个让你构建应用级安全性的框架,但是否能正确理解和使用 Spring Security,取决于你这个开发者本身。Spring Security 本身并不会自动保护应用或静态/传输中的敏感数据。
  • 本书将为你提供有效使用 Spring Security 所需的全部信息。

Spring Security 的替代方案

这本书主要讲的是 Spring Security,但和任何解决方案一样,我总是倾向于先有一个全面的了解。千万不要忘了去了解每种选择的替代方案。随着时间的推移,我体会到,其实没有绝对的对与错。“一切都是相对的”这句话在这里同样适用!

在为 Spring 应用实现安全防护时,你很难找到比 Spring Security 更合适的替代方案。不过,你可以考虑 Apache Shiro(https://shiro.apache.org)。它在配置上非常灵活,并且能够轻松集成到 Spring 和 Spring Boot 应用中。对于某些场景来说,Apache Shiro 有时是 Spring Security 之外不错的选择。

如果你已经使用过 Spring Security,那么上手 Apache Shiro 会觉得非常简单和顺畅。Shiro 提供了自己的注解和基于 HTTP 过滤器的 Web 应用设计,这大大简化了 Web 应用的开发。此外,Shiro 不仅可以保护 Web 应用,还能用于小型命令行、移动应用,甚至大型企业级应用。虽然它的使用方式很简单,但功能却非常强大,能够覆盖从认证、授权到加密、会话管理等各种需求。

然而,Apache Shiro 可能对于你的应用来说过于轻量。Spring Security 不仅仅是一个工具,而是一整套安全解决方案。它提供了更广泛的功能选择,并且是专为 Spring 应用设计的。此外,Spring Security 拥有更庞大且活跃的开发者社区,并在持续不断地优化和完善。

什么是软件安全?#

软件系统需要管理大量数据,其中相当一部分可以被视为敏感信息,尤其是在世界某些地区,比如受到欧洲《通用数据保护条例》(GDPR)要求的地方。任何你作为用户认为属于隐私的信息,对于你的软件应用来说都属于敏感数据。敏感数据可以包括看似无害的信息,比如电话号码、电子邮件地址或身份证号码,尽管我们通常更关注那些一旦泄露风险更高的数据,比如信用卡信息。应用程序应确保这些信息不会被未经授权访问、篡改或拦截。除了数据本身的目标用户外,任何其他方都不应以任何方式与这些数据进行交互。广义上来说,这就是安全性的含义。

Note

GDPR自2018年实施以来,在全球范围内引发了广泛关注。它主要指一套关于数据保护的欧洲法律,旨在让个人对自己的私人数据拥有更多的控制权。GDPR适用于拥有欧洲用户的系统所有者。如果这些应用的所有者未能遵守相关规定,将面临严厉的处罚。

我们采用分层安全策略,每一层都需要不同的防护方法。可以将这些安全层比作一座设防的城堡(见图1.2)。黑客必须突破多重障碍,才能获取应用所管理的资源。你对每一层的防护措施做得越好,恶意人员获取数据或执行未授权操作的可能性就越低。


图1.2 黑暗巫师(黑客)必须突破重重障碍(安全层),才能从公主(你的应用)那里偷走魔法之剑(用户资源)。

安全是一个复杂的话题。对于软件系统来说,安全不仅仅存在于应用层。例如,在网络方面,需要考虑各种问题并采用特定的实践方法;而在存储方面,又是完全不同的讨论。同样,在部署等方面也有不同的理念。Spring Security 是一个属于应用层安全的框架。在本节中,你将对这一安全层级及其相关影响有一个整体的了解。

应用层安全(见图1.3)指的是应用程序为保护其运行环境,以及其处理和存储的数据所应采取的一切安全措施。需要注意的是,这不仅仅关乎应用本身涉及和使用的数据。一个应用程序如果存在漏洞,可能会被恶意人员利用,从而影响整个系统的安全!


图1.3 我们以分层的方式实现安全防护,每一层都建立在前一层的基础之上。本书重点讲解如何使用Spring Security,这是一款在最顶层为应用程序提供安全保障的框架。

为了更直观地说明,我们来讨论一些实际案例。假设我们部署了如图1.4所示的系统。这种情况在采用微服务架构设计的系统中非常常见,尤其是在云端跨多个可用区部署时。

Note

在本书中,我们将使用图1.4来说明应用层安全的概念。你可以将其视为一个示例,帮助你理解应用层安全的基本原理。如果你有兴趣开发高效的云原生 Spring 应用,我强烈推荐 Thomas Vitale 所著的《Cloud Native Spring in Action》(Manning, 2022)。在这本书中,作者全面讲解了专业人士在云端部署 Spring 应用时需要掌握的各个关键方面。

在采用这类服务和微服务架构时,我们可能会遇到各种安全漏洞,因此必须格外小心。正如前面提到的,安全是一个横贯各层的关注点,我们需要在多个层面进行设计。在处理某一层的安全问题时,最佳实践是尽量假设上层并不存在。可以参考图1.2中的城堡类比:如果你负责管理有30名士兵的防线,你就要让他们尽可能强大。即使你知道,在敌人到达他们之前,还需要先跨过那座烈火燃烧的桥梁,你也要做好万全准备。


图1.4 如果恶意用户成功获取了虚拟机(VM)的访问权限,而系统又没有应用应用层安全措施,黑客就有可能控制系统中的其他应用程序。如果通信发生在两个不同的可用区(AZ)之间,恶意分子将更容易拦截消息。这一漏洞可能导致数据被窃取或用户身份被冒用。

考虑到这一点,假设有心怀不轨的人能够登录托管第一个应用程序的虚拟机(VM)。我们还假设第二个应用程序并不会对第一个应用程序发来的请求进行校验。这样,攻击者就可以利用这个漏洞,通过冒充第一个应用程序来控制第二个应用程序。

另外,还要考虑到我们将这两个服务部署在不同的位置。这样,攻击者就不需要登录到其中一台虚拟机上,而是可以直接在这两个应用之间的通信过程中进行中间人攻击。

Note

在云部署中,可用区(如图1.4中的AZ)指的是一个独立的数据中心。这个数据中心在地理位置上与同一地区的其他数据中心有足够的距离隔离(并且在其他依赖性方面也有区分),这样即使某一个可用区发生故障,其他可用区同时故障的概率也极低。从安全角度来看,一个重要的方面是,不同数据中心之间的流量通常需要特别关注,因为这些流量往往会经过公共网络。

我之前提到过身份认证和授权。这两者在大多数应用程序中都存在。通过身份认证,应用程序能够识别用户(无论是个人还是其他应用程序)。识别用户的目的是为了之后能够决定他们可以执行哪些操作——这就是授权。从第三章开始,并贯穿全书,我会详细介绍身份认证和授权的相关内容。

在应用程序开发中,你经常会遇到在不同场景下实现授权的需求。再举一个例子:大多数应用都会对用户访问某些功能进行限制。要实现这一点,首先需要识别是谁发起了访问请求——这就是认证。同时,我们还需要了解用户拥有哪些权限,以决定是否允许其使用系统的某一部分。随着系统的复杂度提升,你会遇到各种需要针对认证和授权进行特殊实现的情况。

例如,如果你希望让系统的某个特定组件以用户的名义访问部分数据或执行某些操作,该怎么办?比如说,打印机需要读取用户的文档。你是不是应该直接把用户的凭证交给打印机?但这样做会赋予打印机超出所需的权限,同时也暴露了用户的凭证。有没有一种合适的方法,既能实现需求,又不用让打印机冒充用户?这些都是开发应用时必须面对的重要问题,也是你在实际开发中经常会遇到的。对于这些问题,本书将通过 Spring Security 的实际应用为你一一解答。

根据你为系统选择的架构,认证和授权不仅会在整个系统层面实现,也会应用到各个组件上。正如你在本书后续内容中会看到的,借助 Spring Security,有时你甚至会希望在同一个组件的不同层级上分别进行授权。在第11章,我们会更详细地讨论方法级安全,这正是指的这一方面。当你需要预先定义一组角色和权限时,系统设计会变得更加复杂。

我还想提醒您注意数据存储的问题。静态数据会增加应用程序的责任。您的应用不应将所有数据都以可读格式存储。有时,应用需要将数据用私钥加密或进行哈希处理。像凭证、私钥这样的敏感信息同样属于静态数据,这些内容应当被妥善保存,通常建议存放在专门的密钥管理系统中。

Note

我们将数据分为“静态数据”和“传输中的数据”。在这里,静态数据是指存储在计算机中的数据,也就是已经持久化的数据。而传输中的数据则指所有在不同节点之间交换的数据。因此,针对不同类型的数据,应采取相应的安全措施。

最后,正在运行的应用程序还必须妥善管理其内部内存。也许听起来有些奇怪,但存储在应用程序堆内的数据同样可能带来安全隐患。有时候,类的设计允许应用长时间保存敏感数据,比如凭证或私钥。在这种情况下,如果有人有权限生成堆转储,就有可能发现这些敏感信息,并加以恶意利用。

通过对这些案例的简要介绍,我希望能够让你对我们所说的应用安全有一个整体的认识,同时也能体会到这一领域的复杂性。软件安全本身就是一个错综复杂的话题。想要成为这一领域的专家,不仅需要理解和应用,还要能够针对系统中各个协作层面进行测试和验证。在本书中,我们将重点介绍你在Spring Security方面需要掌握的所有细节。你将了解这个框架适用的场景和不适用的地方,它能带来哪些帮助,以及为什么值得使用。当然,我们会通过实际的案例来讲解,方便你根据自身的需求进行灵活应用。

为什么安全很重要?#

思考安全性为何重要,最好的切入点就是从你作为用户的角度出发。和其他人一样,你会使用各种应用程序,而这些应用都能访问你的数据。它们可以更改、使用甚至暴露你的数据。想想你用过的所有应用,从电子邮件到网上银行账户。这些系统管理的数据有多敏感,你会如何评估?再想想你能通过这些系统执行的各种操作。同样地,不同的操作重要性也不同。有些操作你可能并不在意,而有些则非常关键。也许你觉得有人偶尔能看到你的部分邮件并不算什么大事,但如果有人能把你的银行账户里的钱都转走,我敢打赌你一定会非常在意。

在你从自己的角度考虑安全性之后,试着换个角度,看看更客观的全貌。同样的数据或操作,对其他人来说可能有着不同程度的敏感性。比如,有些人如果邮箱被访问、邮件被他人阅读,可能会比你更加在意。你的应用程序应确保对所有内容都按照所需的访问级别进行保护。任何导致数据和功能被滥用,或者应用程序影响到其他系统的泄露,都被视为安全漏洞,必须及时修复。

忽视安全问题是要付出代价的,而这个代价我相信你并不愿意承担。通常来说,这主要涉及金钱损失,但实际成本可能各不相同,而且你有可能通过多种方式失去盈利能力。损失不仅仅体现在银行账户里的钱被盗,或者某项服务被免费使用。确实,这些情况都会带来损失,但品牌或公司的形象同样极具价值,失去良好的形象所付出的代价有时甚至比系统漏洞被利用后直接造成的经济损失还要高!用户对你应用的信任是最宝贵的资产之一,这往往决定了你的产品是成功还是失败。

以下是几个虚构的例子。请设想一下,作为用户你会如何看待这些情况。这些问题又会对负责该软件的组织产生怎样的影响?

  • 一个后台管理应用本应管理组织的内部数据,但不知为何,一些信息却泄露了出去。
  • 网约车应用的用户发现,他们的账户因并非自己乘坐的行程被扣了钱。
  • 移动银行应用更新后,用户看到的交易记录竟然属于其他用户。

在第一种情况下,使用该软件的组织及其员工都可能受到影响。有时,公司可能需要承担法律责任,并可能因此遭受重大经济损失。在这种情况下,用户无法自行更改应用程序,但组织可以选择更换软件供应商。

在第二种情况下,用户很可能会选择更换服务提供商。开发该应用的公司的形象将受到极大影响。在这种情况下,金钱上的损失远远小于形象上的损失。即使向受影响的用户退还了款项,应用依然会失去部分客户。这将影响盈利能力,甚至可能导致破产。而在第三种情况下,银行在信任方面可能会遭受严重后果,同时还可能面临法律责任。

在大多数情况下,投入安全防护要比系统被人利用漏洞后造成的后果安全得多。上述每个例子中,只需要一个小小的漏洞就可能导致严重后果。比如第一个例子,可能是身份验证失效或跨站请求伪造(CSRF);第二和第三个例子,可能是缺乏方法访问控制。而对于所有这些情况,也有可能是多种漏洞的组合所引发的问题。

当然,我们还可以进一步探讨与国防相关系统的安全问题。如果你觉得金钱重要,那就再加上人命的代价!你能想象如果医疗系统遭到攻击会造成什么后果吗?那控制核能的系统又会怎样?通过在应用开发初期就投入安全建设,并为安全专家预留充足的时间来设计和测试安全机制,你可以大大降低各种风险。

Note

前人的失败经验告诉我们,遭受攻击所付出的代价,往往远高于提前投入资源来防范漏洞的成本。

在本书接下来的内容中,你将看到如何应用 Spring Security 来避免前面提到的那些安全隐患。关于安全的重要性,恐怕无论怎么强调都不为过。当你不得不在系统安全上做出权衡时,请务必准确评估相关风险。

在这本书中你将学到什么?#

本书以实用为导向,带领读者学习 Spring Security。在接下来的章节中,我们将深入探讨 Spring Security,通过由浅入深的实例来验证相关概念。为了更好地理解本书内容,你需要具备一定的 Java 编程基础,并熟悉 Spring 框架的基本用法。如果你还没有接触过 Spring 框架,或者对其基础知识还不够熟悉,建议你先阅读我写的另一本书《Spring Start Here》(Manning, 2021)。在读完那本书后,你可以通过 Craig Walls 的《Spring 实战》第六版(Manning, 2022)以及 Mark Heckler 的《Spring Boot: Up and Running》(O’Reilly Media, 2021)进一步提升对 Spring 的理解。

在本书中,你将学到:

  • Spring Security 的架构和基本组件,以及如何利用它来保护你的应用程序安全
  • 使用 Spring Security 进行身份认证与授权,包括 OAuth 2 和 OpenID Connect 流程,以及这些技术在生产级应用中的实际应用
  • 如何在应用程序的不同层中实现基于 Spring Security 的安全防护
  • 不同的配置方式及其在项目中的最佳实践
  • 如何在响应式应用中使用 Spring Security
  • 如何对你的安全实现进行测试

为了让每个概念的学习过程更加顺畅,我们将通过多个简单的示例进行讲解。

完成本课程后,你将能够将 Spring Security 应用于最实用的场景,并清楚地了解它的使用场合及最佳实践。我也强烈建议你动手实践每一个配套的示例,加深理解。

总结#

  • Spring Security 是保护 Spring 应用程序的首选方案,针对不同的开发风格和架构,提供了丰富的安全选项。
  • 系统安全应分层实现,每一层都需要采用不同的安全措施和最佳实践。
  • 安全是一个贯穿整个项目生命周期的核心问题,应该在软件开发初期就加以重视和规划。
  • 通常,遭受攻击所带来的损失远高于前期投入以防止漏洞的成本。
  • 有时候,一些微小的疏忽也可能带来严重后果。例如,通过日志或错误信息泄露敏感数据,是应用程序中常见的安全漏洞来源。

评论