CodeGym /Java 博客 /随机的 /Java 中的安全性:最佳实践
John Squirrels
第 41 级
San Francisco

Java 中的安全性:最佳实践

已在 随机的 群组中发布
服务器应用程序中最重要的指标之一是安全性。这是一种非功能性需求Java 中的安全性:最佳实践 - 1安全性包括许多组件。当然,完全涵盖所有已知的安全原则和安全措施需要不止一篇文章,因此我们将重点介绍最重要的内容。精通此主题的人可以设置所有相关流程,避免产生新的安全漏洞,并且任何团队都需要他。当然,您不应认为如果您遵循这些做法,您的应用程序就会 100% 安全。不!但是他们肯定会更安全。我们走吧。

1.提供Java语言级别的安全性

首先,Java 中的安全性从语言的功能级别开始。如果没有访问修饰符我们会怎么做?除了无政府状态,别无他物。编程语言帮助我们编写安全代码,还利用了许多隐式安全功能:
  1. 强打字。Java 是一种静态类型语言。这使得在运行时捕获与类型相关的错误成为可能。
  2. 访问修饰符。这些允许我们根据需要自定义对类、方法和字段的访问。
  3. 自动内存管理。为此,Java 开发人员有一个垃圾收集器,使我们不必手动配置所有内容。是的,有时会出现问题。
  4. 字节码验证:Java被编译成字节码,在执行之前由运行时检查。
此外,还有Oracle 的安全建议。当然,这本书不是用高深的语言写成的,读起来可能会睡着好几次,但这是值得的。特别是,名为Secure Coding Guidelines for Java SE 的文档非常重要。它提供有关如何编写安全代码的建议。该文档传达了大量非常有用的信息。如果你有机会,你一定要读一读。为了激发您对此材料的兴趣,这里有一些有趣的提示:
  1. 避免序列化安全敏感类。序列化暴露了序列化文件中的类接口,更不用说序列化的数据了。
  2. 尽量避免数据的可变类。这提供了不可变类的所有好处(例如线程安全)。如果您确实有一个可变对象,它可能会导致意外行为。
  3. 制作返回的可变对象的副本。如果方法返回对内部可变对象的引用,则客户端代码可以更改对象的内部状态。
  4. 等等…
基本上,Java SE 安全编码指南是有关如何正确、安全地编写 Java 代码的提示和技巧的集合。

2.消除SQL注入漏洞

这是一种特殊的漏洞。它很特别,因为它既是最著名的漏洞之一,也是最常见的漏洞之一。如果您从未对计算机安全感兴趣,那么您将不会了解它。什么是SQL注入?这是一种数据库攻击,涉及在不希望的地方注入额外的 SQL 代码。假设我们有一个方法接受某种参数来查询数据库。例如,用户名。易受攻击的代码看起来像这样:

// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
  
   // Compose a SQL database query with our firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
  
   // Execute the query
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
在本例中,SQL 查询是预先在单独的行中准备的。那有什么问题,对吧?也许问题是使用String.format会更好?不?那么,然后呢?让我们设身处地为测试人员想想什么可以作为firstName的值传递。例如:
  1. 我们可以传递预期的内容——用户名。然后数据库将返回所有具有该名称的用户。
  2. 我们可以传递一个空字符串。然后将返回所有用户。
  3. 但我们也可以传递以下内容:“'; DROP TABLE USERS;”。在这里,我们现在遇到了 huuuuuuge 问题。此查询将从数据库中删除一个表。连同所有数据。所有的。
你能想象这会导致什么问题吗?除此之外,你可以写任何你想写的。您可以更改所有用户的名称。您可以删除他们的地址。破坏的范围是巨大的。为避免这种情况,您需要防止注入现成的查询,而是使用参数形成查询。这应该是创建数据库查询的唯一方法。这就是消除此漏洞的方法。例如:

// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Create a parameterized query.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Create a prepared statement with the parameterized query
   PreparedStatement statement = connection.prepareStatement(query);
  
   // Pass the parameter's value
   statement.setString(1, firstName);

   // Execute the query
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
这样就避免了漏洞。对于那些想要更深入地研究本文的人,这里有一个很好的例子。您如何知道何时了解此漏洞?如果你看到下面漫画中的笑话,那么你可能已经清楚地了解这个漏洞是怎么回事了:DJava 中的安全性:最佳实践 - 2

3. 扫描依赖并保持更新

这意味着什么?如果你不知道什么是依赖,我会解释。依赖项是一个 JAR 存档,其中包含使用自动构建系统(Maven、Gradle、Ant)连接到项目的代码,以便重用其他人的解决方案。比如Project Lombok,它在运行时为我们生成getters、setters等。大型应用程序可能有很多依赖项。有些是可传递的(也就是说,每个依赖项可能有自己的依赖项,等等)。因此,攻击者越来越关注开源依赖项,因为它们经常被使用,许多客户端可能因此而出现问题。重要的是要确保整个依赖树(是的,它看起来像一棵树)中没有已知的漏洞。做这件事有很多种方法。

使用 Snyk 进行依赖监控

Snyk检查所有项目依赖项并标记已知漏洞。您可以在 Snyk 上注册并通过 GitHub 导入您的项目。Java 中的安全性:最佳实践 - 3此外,如上图所示,如果新版本修复了漏洞,Snyk 将提供修复并创建拉取请求。您可以免费将其用于开源项目。定期扫描项目,例如每周一次、每月一次。我注册并将我所有的公共存储库添加到 Snyk 扫描中(这没有什么危险,因为它们已经对所有人公开)。Snyk 然后显示扫描结果:Java 中的安全性:最佳实践 - 4过了一会儿,Snyk-bot 在需要更新依赖项的项目中准备了几个拉取请求:Java 中的安全性:最佳实践 - 5还有:Java 中的安全性:最佳实践 - 6这是查找漏洞和监控新版本更新的好工具。

使用 GitHub 安全实验室

任何在 GitHub 上工作的人都可以利用其内置工具。您可以在他们名为Announcing GitHub Security Lab 的博客文章中阅读有关此方法的更多信息。这个工具当然比 Snyk 简单,但您绝对不应该忽视它。更重要的是,已知漏洞的数量只会增加,因此 Snyk 和 GitHub 安全实验室都将继续扩展和改进。

启用 Sonatype DepShield

如果您使用 GitHub 来存储您的存储库,则可以将 Sonatype DepShield(MarketPlace 中的应用程序之一)添加到您的项目中。它还可用于扫描项目的依赖项。此外,如果它找到了什么,将生成一个 GitHub Issue,并带有适当的描述,如下所示:Java 中的安全性:最佳实践 - 7

4. 小心处理机密数据

我们也可以使用短语“敏感数据”。泄露客户的个人信息、信用卡号码和其他敏感信息可能会造成无法弥补的伤害。首先,仔细查看您的应用程序的设计并确定您是否真的需要这个或那个数据。也许您实际上并不需要您拥有的某些数据——为尚未到来且不太可能到来的未来添加的数据。此外,许多人无意中通过日志记录泄露了此类数据。防止敏感数据进入日志的一种简单方法是清除域实体(例如用户、学生、教师等)的toString()方法。这将防止您意外输出机密字段。如果您使用 Lombok 生成toString()方法,您可以使用@ToString.Exclude注释来防止在toString()方法的输出中使用某个字段。另外,向外界发送数据时要非常小心。假设我们有一个显示所有用户名称的 HTTP 端点。无需显示用户的唯一内部 ID。为什么?因为攻击者可以使用它来获取有关用户的其他更敏感的信息。例如,如果您使用 Jackson 将POJO序列化/反序列化为JSON,那么您可以使用@JsonIgnore@JsonIgnoreProperties注释以防止特定字段的序列化/反序列化。一般来说,你需要在不同的地方使用不同的 POJO 类。这意味着什么?
  1. 使用数据库时,使用一种类型的 POJO(实体)。
  2. 使用业务逻辑时,将实体转换为模型。
  3. 在与外界合作并发送 HTTP 请求时,使用不同的实体 (DTO)。
这样您就可以清楚地定义哪些字段从外部可见,哪些不可见。

使用强大的加密和哈希算法

客户的机密数据必须安全存储。为此,我们需要使用加密。根据任务,您需要决定使用哪种类型的加密。此外,更强大的加密需要更多时间,因此您需要再次考虑对它的需求在多大程度上证明花在它上面的时间是合理的。当然,你可以自己写一个加密算法。但这是不必要的。您可以在这方面使用现有的解决方案。例如,谷歌 Tink

<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
   <groupid>com.google.crypto.tink</groupid>
   <artifactid>tink</artifactid>
   <version>1.3.0</version>
</dependency>
让我们看看要做什么,使用这个涉及加密和解密的例子:

private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Elvis lives!";
   String aad = "Buddy Holly";

   Aead aead = handle.getPrimitive(Aead.class);
   byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
   String encryptedString = Base64.getEncoder().encodeToString(encrypted);
   System.out.println(encryptedString);

   byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
   System.out.println(new String(decrypted));
}

加密密码

对于这个任务,使用非对称加密是最安全的。为什么?因为应用程序并不真正需要解密密码。这是标准方法。实际上,当用户输入密码时,系统会对其进行加密并将其与密码存储中的内容进行比较。执行相同的加密过程,所以我们可以预期它们会匹配,如果输入正确的密码,当然 :) BCrypt 和 SCrypt 在这里很合适。两者都是单向函数(加密哈希),具有计算复杂的算法,需要很长时间。这正是我们所需要的,因为直接计算将花费很长时间(好吧,很长很长时间)。Spring Security 支持一整套算法。我们可以使用SCryptPasswordEncoderBCryptPasswordEncoder. 目前被认为是强加密算法的算法明年可能会被认为是弱的。因此,我们得出结论,我们应该定期检查我们使用的算法,并根据需要更新包含加密算法的库。

而不是结论

今天我们谈到了安全,自然而然地,很多事情都被抛在了幕后。我刚刚为你打开了通往新世界的大门,一个拥有自己生命的世界。安全就像政治,你不忙于政治,政治就会忙于你。我传统上建议您在GitHub 帐户上关注我。我在那里发布我的创作,涉及我正在研究和在工作中应用的各种技术。

有用的链接

  1. Guru99:SQL 注入教程
  2. Oracle:Java 安全资源中心
  3. Oracle:Java SE 安全编码指南
  4. Baeldung:Java 安全基础
  5. 中:增强 Java 安全性的 10 个技巧
  6. Snyk:10 个 Java 安全最佳实践
  7. GitHub:宣布 GitHub 安全实验室:共同保护世界代码
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION