Java的“final”关键字是否真的提高了安全性?

21

虽然在Java中使用“final”关键字有许多原因,但我一直听到的一个原因是它可以使代码更加安全。尽管在这种简单的情况下似乎有道理:

public class Password
{
    public final String passwordHash;
    ...
}

使用final关键字,您期望没有恶意代码能够更改变量passwordHash。但是,使用反射可以更改passwordHash字段上的final修饰符。
那么'final'是否提供任何真正的安全性,还是只是安慰剂? 编辑: 有一些非常有趣的讨论,我希望我可以接受不止一个答案。感谢大家的参与。

这里有一个链接,提供了几个由Oracle编写的示例,演示了如何使用_final_关键字来实现安全目的。 - jahroy
12个回答

39

这里的“security”并不是指“抵御攻击”的意思;更像是“不易犯错误”。

我更喜欢用“安全性”,因为我认为它更像是预防事故,而不是恶意行为。


1
看起来人们在2010年比现在聪明多了 - https://dev59.com/aWMl5IYBdhLWcg3wBzDy#18755903 - user93353
我觉得很惊奇,那个其他的线程引起了这么多争论。 耸肩 - Andres
如果不将类或方法标记为“final”,那么它们就有可能被攻击者恶意覆盖。 - The Gilbert Arenas Dagger

11

Java中的final关键字不用于这种类型的安全性。它不能替代通常需要使用加密解决方案的内容。

在这些讨论中,“安全”通常是指一个安全的对象模型的概念 - 即一个对象模型,消费者无法将其用于原类的意外目的。


它提供什么类型的安全性? - Andres
所以你的意思是它保护了对象模型,但并没有保护对象内部数据的完整性? - Andres
3
一个构造得很好的类,会封装它的状态。如果一个类只有final方法,消费者就无法覆盖类的行为并改变如何封装状态。一个类可以公开其状态(例如通过公共字段),而没有final关键字则无法保护该状态的完整性。 - Andrew Hare

8

我不确定在我的系统中是否应该依赖语言结构来增加安全性。

我认为将字段设置为final并不能增加对恶意攻击的安全性(更可能是针对错误和线程问题)。唯一的“真正形式”安全性是,如果您有一个final常量字段,它可能会在编译时被内联化,因此在运行时更改其值不会产生影响。

我听说过final和安全性更多是在继承的上下文中。通过使类final,您可以防止某人对其进行子类化并触及或覆盖其受保护成员,但再次,我更多地使用它来避免错误而不是预防威胁。


4
总的来说,final、private和其他类似结构应被视为一般性偏好的陈述,而非严格强制的安全措施。
但是,如果您控制JVM进程正在运行的进程(例如,在您的JVM上运行由他人提供的代码),那么final和private确实可以提供安全性 - 配合Java的SecurityManager。通过反射可以绕过这些限制的事情可以被预防。
您不能在将代码发送到别人的JVM以运行并认为以此隐藏任何内容。
编辑:汤姆提醒我,适当使用final字段也可以在一定程度上防止序列化攻击(即故意提供不良二进制流的序列化数据)。Effective Java包括更多此类示例。

4

安全防范什么?

在移动代码方面(可以在系统之间移动的代码 - applet、midlet、WebStart、RMI/JINI等),这非常重要。应用于类和部分可访问方法,它可以防止恶意实现。同样的,对于可访问字段,特别是静态字段也是如此。如果您要编写可能成为库一部分的代码,则需要高度警觉。

对于典型的网络或桌面应用程序代码而言,这远不及那么重要。然而,如果缺少字段上的它,代码会变得更难阅读,并且表明程序员混乱。这样的代码由于编写不良而不太可能是安全的。


如果你要编写可能成为库的代码,你需要非常清楚这一点。 - jahroy

3

它并不会让你的代码更安全,它更多是为了线程安全而设计的。如果一个变量被标记为final,在对象创建时必须给它赋值。在对象创建后,该变量不能再指向另一个值。

这种行为允许你推理出对象的状态,并在多个线程同时访问它时做出某些假设。


2

我想,当有人说final会使你的代码更安全时,他们的意思是它可以防止未来的开发者修改不应被修改的值,或继承不应被扩展的类(从而导致不可预测的结果)。这与身份验证没有直接关系。


2

我相信final关键字在设计上的作用和类声明中的访问修饰符类似,是一种表达和强制执行设计的方式。


1

“final”关键字确实有一些安全方面的影响。假设您正在设计一个安全系统,该系统具有一个服务,如果并且仅当字符串有效时才执行某些操作。您可能会编写:



public void doSomethingUseful(String argument) {
    checkValidity(argument);
    /* prepare to do something useful... preparation takes a bit of time! */
    reallyDoTheUsefulTask(argument);
}

如果String不是final的话,一些聪明的攻击者可以对其进行子类化。他们的字符串不像标准的String类那样是不可变的 - 实际上,他们可以生成另一个线程,并在checkValidity之后但在您实际使用参数之前尝试对您的方法进行攻击,从而更改参数。然后,您的“有用任务”突然执行完全错误的操作,可能会危及安全性。他们刚刚绕过了您的检查!然而,由于java.lang.String是final的,因此您可以很好地保证当您请求String参数时,它实际上是标准的不可变String。这是非常重要的 - 基于系统调用的不正确参数处理周围存在整个内核模式攻击类别。

所以,final确实有一些安全考虑因素。


你能想到为什么最终字段(final fields)而不是最终类(final classes)会提供更好的安全性吗? - Andres
你不能对String进行子类化,因为它是final的! - user177800
是的,他提到了。如果你读完整个内容,你会意识到他在解释为什么它是最终的。 - Andres
1
虚假的安全感。如果有人能够在您的系统上执行他的代码,他可以执行完全相同的代码,而不是“巧妙地保护”您的类所做的事情,而是“巧妙地强制”您的类去做他想要的事情。 - Danubian Sailor
1
并不是完全正确的。Java确实拥有一个安全模型,而来自不同“ClassLoader”的代码可以具有不同的权限。你可以认为它相当破碎(尤其是沙盒机制),但它确实存在,且不可变类会影响它的使用方式。 - Steven Schlansker

1

final关键字通常用于保持不可变性。将final用于类或方法是为了防止方法之间的链接被破坏。例如,假设类X的某个方法的实现假定方法M会以某种方式运行。声明X或M为final将防止派生类重新定义M,从而导致X的行为不正确。它可以保护对象和方法免受操纵。但是对于加密目的,使用final关键字并非解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接