安全现状#
约 6144 个字 3 张图片 预计阅读时间 123 分钟
开发人员越来越意识到安全软件的重要性,并且从软件开发的初期就开始承担起安全责任。通常,作为开发人员,我们首先了解到应用程序的目的是解决业务问题。这个目的指的是某种数据处理、持久化,最终按照需求展示给用户的过程。这种从学习这些技术的早期阶段就被灌输的软件开发概述,不幸地掩盖了许多同样是开发过程一部分的实践。虽然从用户的角度来看,应用程序运行正常,并且在功能上达到了用户的预期,但最终结果中隐藏了许多方面。
非功能性软件质量,如性能、可扩展性、可用性和安全性等,都会在短期和长期内产生影响。如果在早期没有考虑到这些质量因素,它们可能会显著影响应用程序所有者的盈利能力。此外,忽视这些因素还可能引发其他系统的故障(例如,被迫参与分布式拒绝服务[DDoS] 攻击)。非功能性需求的隐藏特性(即很难察觉到某些东西是否缺失或不完整)使得这些问题更加危险。
在开发软件系统时,有多个非功能性方面需要考虑。实际上,这些方面都很重要,在软件开发过程中需要负责任地对待。本书重点关注其中一个方面:安全性。你将学习如何使用Spring Security一步步保护你的应用程序。
本章将为您展示与安全相关概念的全貌。在整本书中,我们将通过实际例子进行讲解,并在适当的时候回顾本章中的描述。在适用的情况下,我还会为您提供更多细节。在书中,您还会发现一些关于特定主题的参考资料(书籍、文章和文档),这些资料对进一步阅读非常有用。
探索Spring Security#
本节讨论了Spring Security与Spring之间的关系。首先,在开始使用它们之前,了解两者之间的联系是很重要的。如果我们查看官方网站 ,可以看到Spring Security被描述为一个强大且高度可定制的认证和访问控制框架。我会简单地说,它是一个极大简化了为Spring应用程序应用(或嵌入)安全性的框架。
Spring Security 是在 Spring 应用中实现应用级安全的首选方案。通常,它的目的是为您提供一种高度可定制的方式来实现身份验证
、 授权
以及防御常见攻击
。Spring Security 是在 Apache 2.0 许可证下发布的开源软件。您可以在 GitHub 上访问其源代码,。我强烈建议您也为该项目做出贡献。
Note
您可以将Spring Security用于标准的Web Servlets和响应式应用程序,以及非Web应用程序。在本书中,我们将使用最新的Java长期支持版本、Spring和Spring Boot版本(Java 21
、Spring 6
和Spring Boot 3
)来讲解Spring Security。然而,本书中的所有示例也适用于之前的长期支持版本Java 17
。
Spring Security是为Spring应用实现应用级安全的事实标准。然而,Spring Security并不会自动保护你的应用。它并不是某种能保证应用无漏洞的神奇灵丹妙药。开发者需要了解如何根据应用的需求配置和定制Spring Security。如何做到这一点取决于许多因素,从功能需求到架构设计。
从技术上讲,在 Spring 应用中使用 Spring Security 实现安全性是很简单的。你已经实现了 Spring 应用,所以你知道这个框架的理念是从管理 Spring 上下文开始的。你在 Spring 上下文中定义 bean
,以便框架根据你指定的配置来管理这些 bean。
你使用注解来告诉 Spring 该做什么:暴露端点
、在事务中包装方法
、在切面中拦截方法
等等。Spring Security 配置也是如此,这就是 Spring Security 发挥作用的地方。你希望在定义应用级别的安全性时,能够舒适地使用注解
、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应用程序方面,你不会找到很多Spring Security的替代方案。你可以考虑的一个替代方案是Apache Shiro。它在配置上提供了灵活性,并且很容易与Spring和Spring Boot应用程序集成。Apache Shiro有时是Spring Security方法的一个不错的替代选择。
如果你已经使用过Spring Security,你会发现使用Apache Shiro既简单又舒适。它提供了自己的注解和基于HTTP过滤器的Web应用程序设计,大大简化了Web应用程序的工作。此外,你可以用Shiro保护的不仅仅是Web应用程序,从较小的命令行和移动应用程序到大规模的企业应用程序都可以。而且,尽管简单,它足够强大,可以用于广泛的用途——从身份验证和授权到加密和会话管理。
然而,Apache Shiro可能对你的应用程序需求来说过于轻量。Spring Security不仅仅是一把锤子,而是一整套工具。它提供了更大规模的可能性,并且专为Spring应用程序设计。此外,它受益于一个更大的活跃开发者社区,并且不断得到增强。
什么是软件安全?#
软件系统管理着大量数据,其中相当一部分可以被视为敏感数据
,尤其是在世界某些地区,例如根据欧洲《通用数据保护条例》(GDPR)
的要求。任何你作为用户认为私密的信息,对于你的软件应用程序来说都是敏感的。敏感数据可以包括无害的信息
,如电话号码、电子邮件地址或身份证号码,尽管我们通常更关注那些丢失风险更高的数据
,比如信用卡信息。应用程序应确保这些信息不会被 访问
、更改
或拦截
。除了这些数据的预期用户外,任何其他方都不应以任何方式与其互动。广义上讲,这就是安全的含义。
Note
GDPR自2018年推出以来在全球引起了广泛关注。它主要代表了一套关于数据保护的欧洲法律,赋予人们对其私人数据更多的控制权。GDPR适用于拥有欧洲用户的系统所有者。如果这些应用程序的所有者不遵守规定,将面临重大处罚。
我们采用分层安全策略,每一层都需要不同的方法。可以将这些层比作一座受保护的城堡(图1.2)。黑客需要绕过多个障碍才能获取应用程序管理的资源。你对每一层的安全措施做得越好,恶意人员访问数据或执行未经授权操作的可能性就越低。
安全是一个复杂的话题。在软件系统中,安全不仅仅存在于应用层面。例如,在网络方面
,有需要考虑的问题和特定的实践方法,而在存储方面
,则是另一个完全不同的讨论。同样,在部署方面也有不同的理念等等。Spring Security 是一个属于应用层面安全的框架
。在本节中,你将对这一安全层面及其影响有一个大致的了解。
应用级别
的安全性(图1.3)指的是应用程序应采取的一切措施,以保护其运行环境以及处理和存储的数据。请注意,这不仅仅涉及应用程序影响和使用的数据。一个应用程序可能存在漏洞,使得恶意人员能够影响整个系统!
为了更明确地说明这一点,我们来讨论一些实际案例。我们将考虑一个如图1.4所示的系统部署情况。这种情况在使用微服务架构设计的系统中很常见,尤其是在云中部署到多个可用区时。
Note
如果你有兴趣实现高效的云端Spring应用,我强烈推荐托马斯·维塔莱的《Cloud Native Spring in Action》(Manning出版社,2022年)。在这本书中,作者专注于专业人士需要了解的所有方面,以便实现优秀的云部署Spring应用。
在使用这些服务和微服务架构时,我们可能会遇到各种漏洞,因此需要格外小心。如前所述,安全性是一个跨越多个层面的关注点。处理某一层的安全问题时,最佳实践是假设上层尽可能不存在。想想图1.2中的城堡类比。如果你管理一层有30名士兵的防线,你会希望他们尽可能强大。即使你知道在到达他们之前,敌人需要先穿过燃烧的桥梁,你仍然会这样做。
考虑到这一点,我们假设一个怀有恶意的人能够登录托管第一个应用程序的虚拟机(VM)。我们还假设第二个应用程序不会验证由第一个应用程序发送的请求。攻击者可以利用这一漏洞,通过冒充第一个应用程序来控制第二个应用程序。
另外,考虑到我们将这两个服务部署到两个不同的位置。那么,攻击者不需要登录到其中一个虚拟机,因为他们可以直接在两个应用程序之间的通信中进行中间人攻击。
Note
在云部署中,可用区(图1.4中的AZ)指的是一个独立的数据中心。这个数据中心在地理位置上与同一区域的其他数据中心有足够的距离(并且有其他依赖关系),以至于如果一个可用区发生故障,其他可用区发生故障的概率极小。在安全性方面,一个重要的方面是两个不同数据中心之间的流量通常需要特别注意,因为它通常会经过公共网络。
我之前提到过身份验证
和授权
。这些功能在大多数应用程序中都有。通过身份验证
,应用程序可以识别用户(无论是个人还是其他应用程序)。识别用户的目的是为了之后能够决定他们可以做什么——这就是授权
。
在应用程序中,你经常需要在不同场景下实现授权
。考虑另一种情况:大多数应用程序对用户访问某些功能都有一定的限制。要实现这一点,首先需要识别谁在请求特定功能的访问权限——这就是 身份验证
。我们还需要了解他们的权限,以允许用户使用系统的那部分功能。随着系统变得更加复杂,你会发现不同的情况需要与身份验证和授权相关的特定实现。
例如,如果您希望授权系统的某个特定组件代表用户对一部分数据或操作进行访问,该怎么办呢?假设打印机需要读取用户的文档。您是否应该直接将用户的凭证分享给打印机?但这样做会赋予打印机超出所需的权限,同时也暴露了用户的凭证。有没有一种不冒充用户的正确方法来实现这一点呢?这些都是开发应用程序时会遇到的重要问题,也是我们希望解答的问题。
根据所选的系统架构,您会发现整个系统以及任何组件都存在身份验证
和授权
。正如您在本书后面会看到的,有了Spring Security,有时您甚至会倾向于在同一组件的不同层次使用授权。当您有一组预定义的角色和权限时,设计会变得更加复杂。
我还想提醒您注意数据存储
的问题。静态数据增加了应用程序的责任。您的应用程序不应以可读格式存储所有数据。有时,应用程序需要使用 私钥加密
或哈希
处理数据。像凭证
和私钥
这样的机密信息也可以视为静态数据。这些信息应被妥善存储,通常保存在秘密库中。
Note
我们将数据分类为静态数据
或传输中的数据
。在这种情况下,静态数据
是指存储在计算机中的数据,换句话说,就是持久化的数据
。传输中的数据
则指从一个点到另一个点交换的所有数据。因此,应根据数据类型采取不同的安全措施。
最后,执行应用程序还必须管理其内部内存
。听起来可能有些奇怪,但存储在应用程序堆中的数据也可能存在漏洞。有时,类设计允许应用程序长时间存储敏感数据,如凭证或私钥。在这种情况下,有权限进行堆转储的人可能会发现这些细节,并恶意使用它们。
通过对这些案例的简要描述,我希望能为您提供一个关于应用安全的概述,并展示这一主题的复杂性。软件安全是一个错综复杂的话题。想要成为这一领域的专家,需要理解(并应用)然后测试系统中各层协作的解决方案。然而,在本书中,我们将专注于介绍您在Spring Security方面需要了解的所有细节。您将了解这个框架的适用范围及其局限性,它如何提供帮助,以及为什么应该使用它。当然,我们会通过实际示例来进行讲解,您可以将这些示例适应到您自己的独特用例中。
为什么安全很重要?#
从用户的角度来看,思考安全性为何重要的最佳方式是:像其他人一样,你使用各种应用程序,而这些应用程序可以访问你的数据。它们可以更改、使用或暴露你的数据。想想你使用的所有应用程序,从电子邮件到在线银行服务账户。你会如何评估这些系统管理的数据的敏感性?你可以使用这些系统执行的操作又如何呢?与数据类似,有些操作比其他操作更重要。你可能不太在意某些操作,但对其他操作则非常重视。也许对你来说,如果有人设法读取你的一些电子邮件,这并不那么重要。但我敢打赌,如果有人能清空你的银行账户,你一定会非常在意。
一旦你从自己的角度考虑了安全性,试着从更客观的角度来看待问题。同样的数据或行为对其他人可能有不同程度的敏感性。有些人可能比你更在意他们的电子邮件被访问和阅读。你的应用程序应确保保护所有内容达到所需的访问级别。任何允许数据和功能被利用的泄漏,以及影响其他系统的应用程序,都被视为漏洞,需要解决。
不重视安全会付出代价,而我相信这是你不愿意承担的。通常,这涉及到金钱。但成本可能各不相同,有多种方式会导致盈利能力下降。这不仅仅是银行账户里的钱被盗或使用服务而不付费。这些确实意味着成本。品牌或公司的形象同样珍贵,失去良好形象的代价可能很高——有时甚至比系统漏洞被利用所直接导致的费用还要高!用户对你应用的信任是其最宝贵的资产之一,它可以决定成败。
以下是几个虚构的例子。想象一下,作为用户你会怎么看待这些情况。这些情况会如何影响负责该软件的组织?
- 后台应用程序应该管理一个组织的内部数据,但不知为何,一些信息泄露了出去。
- 使用拼车应用的用户发现,他们的账户因并非自己乘坐的行程被扣款。
- 更新后,移动银行应用的用户会看到属于其他用户的交易记录。
在第一种情况下,使用该软件的组织及其员工可能会受到影响。在某些情况下,公司可能会承担责任并损失大量资金。在这种情况下,用户无法选择更换应用程序,但组织可以决定更换软件供应商。
在第二种情况下,用户可能会选择更换服务提供商。开发该应用程序的公司的形象将受到极大影响。在这种情况下,金钱上的损失远小于形象上的损失。即使向受影响的用户退还款项,应用程序仍会失去一些客户。这会影响盈利,甚至可能导致破产。而在第三种情况下,银行可能会在信任方面面临严重后果,还可能引发法律纠纷。
在大多数情况下,投资于安全措施比让系统漏洞被人利用要安全得多。对于所有的例子来说,仅仅一个小小的弱点就可能导致严重后果。第一个例子中,可能是身份验证失效或跨站请求伪造(CSRF)。第二个和第三个例子中,可能是缺乏方法访问控制。而对于所有的例子来说,这些问题可能是多种漏洞的组合。
当然,我们还可以进一步讨论与国防相关系统的安全性。如果你认为金钱重要,那就再加上人命的代价!你能想象如果医疗系统受到影响会有什么后果吗?那控制核能的系统呢?通过在应用程序的安全性上早期投资,并为安全专家提供足够的时间来开发和测试安全机制,你可以降低任何风险。
Note
从前人失败的经验中得知,攻击的代价通常高于预防漏洞的投入成本。
我想关于安全性的重要性,无论写多少都不为过。当你不得不在系统安全性上做出妥协时,尽量正确评估你的风险。