java安全编码指南之:Mutability可变性详解

 更新时间:2020年09月14日 11:35:31   作者:flydean程序那些事  
这篇文章主要介绍了java安全编码指南之:Mutability可变性详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

简介

mutable(可变)和immutable(不可变)对象是我们在java程序编写的过程中经常会使用到的。

可变类型对象就是说,对象在创建之后,其内部的数据可能会被修改。所以它的安全性没有保证。

而不可变类型对象就是说,对象一旦创建之后,其内部的数据就不能够被修改,我们可以完全相信这个对象。

虽然mutable对象安全性不够,但是因为其可以被修改,所以会有效的减少对该对象的拷贝。

而immutable对象因为不可改变,所以尝试对该对象的修改都会导致对象的拷贝,从而生成新的对象。

我们最常使用的String就是一个immutable对象。

那么可变性在java的安全编码中的最佳实践是怎么样的呢? 一起来看看吧。

可变对象和不可变对象

知道了可变对象和不可变对象的不同之处之后,我们看一下怎么才能判断这个对象是可变对象还是不可变对象呢?

首先,最简单的一点就是,不可变对象创建之后就不能够被修改,所以不可变对象里面基本上没有setXXX之类的方法,而可变对象提供了setXXX这些可以修改内部变量状态的方法。

看一个例子java.util.Date是一个可变对象,而java.time.LocalTime是不可变对象。

看下他们的方法定义有什么区别呢?

首先是Date,我们可以看到在其中定义了很多setXXX方法。

而在LocalTime中,我们基本上看不到setXXX方法。

同时不可变对象的字段基本上都是final的,防止被二次修改。

第二,不可变对象一般来说是不可继承的,在java中就是以final关键字做限定的:

public class Date

public final class LocalTime

第三,不可变对象一般会隐藏构造函数,而是使用类似工厂模式的方法来创建对象,这样为实例的创建提供了更多的机动性。

创建mutable对象的拷贝

那么如果我们想使用mutable对象,又不想被别人修改怎么办呢?

简单的办法就是拷贝一份要使用的对象:

public class CopyOutput {
      private final java.util.Date date;
      ...
      public java.util.Date getDate() {
        return (java.util.Date)date.clone();
      }
    }

这里大家还要注意深拷贝和浅拷贝的问题。

为mutable类创建copy方法

既然要为mutable对象创建拷贝,那么相应的mutable类也需要提供一个copy方法来协助拷贝。

这里需要考虑一个深拷贝和浅拷贝的问题。

不要相信equals

我们知道在HashMap中怎么去查找一个key呢?先去找这个key的hash值,然后去判断key.equals方法是否相等,考虑下面这种情况:

private final Map<Window,Extra> extras = new HashMap<>();

    public void op(Window window) {
      Extra extra = extras.get(window);
    }

op方法接收一个Window对象,然后将其当成key从HashMap中取出对应的value。

如果,这个时候,我们有一个类A继承了Window,并且hash值和equals都和另外一个Window对象B相同,那么使用A这个key可以获取到B这个key存储的数据!

怎么解决这个问题呢?

Java中有一个特别的HashMap:IdentityHashMap,这个Map的key和value比较是用==而不是equals方法,所以可以有效的避免上面出现的问题。

private final Map<Window,Extra> extras = new IdentityHashMap<>();

    public void op(Window window) {
      Extra extra = extras.get(window);
    }

如果没有这样的Map可用,那么可以使用不可变对象作为key或者使用Window的私有变量,从而恶意攻击者无法获得这个变量。

public class Window {
      /* pp */ 
      class PrivateKey {
        Window getWindow() {
          return Window.this;
        }
      }
      final PrivateKey privateKey = new PrivateKey();

      private final Map<Window.PrivateKey,Extra> extras =
         new WeakHashMap<>();
      ...
    }

    public class WindowOps {
      public void op(Window window) {
        // Window.equals may be overridden,
        // but safe as we don't use it.
        Extra extra = extras.get(window.privateKey);
        ...
      }
    }

不要直接暴露可修改的属性

如果一个可变类中的某个属性确实需要暴露被外部使用,那么一定要将这个属性定义为private,并且使用wrapper方法将其包装起来。

如果直接暴露出去,那么基本上就没有权限控制可言,任何程序只要能够拿到你这个对象,就可以对属性进行修改。考虑下下面的应用方式,我们在修改state的方法中加入了一个参数校验和权限控制。

public final class WrappedState {
      // private immutable object
      private String state;

      // wrapper method
      public String getState() {
        return state;
      }

      // wrapper method
      public void setState(final String newState) {
        this.state = requireValidation(newState);
      }

      private static String requireValidation(final String state) {
        if (...) {
          throw new IllegalArgumentException("...");
        }
        return state;
      }
    }

public static fields应该被置位final

同样的,如果你是一个类变量,当然不希望这个变量会被任何人修改,那么需要将其置位final。

public class Files {
      public static final String separator = "/";
      public static final String pathSeparator = ":";
    }

public static final field 应该是不可变的

如果类变量是public static final的,那么这个变量一定要是不可变的。

有人会问了,都定义成了final了,是不是就已经不可变了?

其实不然,比如我们定义了一个final的List,虽然这个list不能变化,但是list里面的值是可以变化的。我们需要将可变变量修改为不可变变量,如下所示:

import static java.util.Arrays.asList;
    import static java.util.Collections.unmodifiableList;
    ...
    public static final List<String> names = unmodifiableList(asList(
      "Fred", "Jim", "Sheila"
    ));

如果使用JDK9中引入的of()或者ofEntries()方法,可以直接创建不可修改的集合:

public static final List
<String> names =
 List.of("Fred", "Jim", "Sheila");

以上这篇java安全编码指南之:Mutability可变性详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Spring生命周期回调与容器扩展详解

    Spring生命周期回调与容器扩展详解

    这篇文章主要介绍了Spring生命周期回调与容器扩展详解,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • java交换排序之奇偶排序实现方法

    java交换排序之奇偶排序实现方法

    这篇文章主要介绍了java交换排序之奇偶排序实现方法,实例分析了奇偶排序的原理与具体实现技巧,非常具有实用价值,需要的朋友可以参考下
    2015-02-02
  • java 常规轮询长轮询Long polling实现示例详解

    java 常规轮询长轮询Long polling实现示例详解

    这篇文章主要为大家介绍了java 常规轮询长轮询Long polling实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Springboot打包部署代码实例

    Springboot打包部署代码实例

    这篇文章主要介绍了Springboot打包部署代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Java三个类加载器及它们的相互关系

    Java三个类加载器及它们的相互关系

    Java在需要使用类别的时候,才会将类别加载,Java的类别载入是由类别载入器(Class loader)来达到的,预设上,在程序启动之后,主要会有三个类别加载器,文中详细介绍了这三个类加载器,需要的朋友可以参考下
    2021-06-06
  • 聊一聊Java中的Steam流

    聊一聊Java中的Steam流

    当我们需要处理的数据量很大的时候,为了提高性能,就需要使用到并行处理,这样的处理方式是很复杂的,流可以帮助开发者节约宝贵的时间,让以上的事情变得轻松,本文就和大家聊一聊Java中的Steam流,感兴趣的同学跟着小编一起来看看吧
    2023-07-07
  • Java Collections.sort()排序代码案例

    Java Collections.sort()排序代码案例

    这篇文章主要介绍了Java Collections.sort()排序代码案例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • 基于Java设计一个高并发的秒杀系统

    基于Java设计一个高并发的秒杀系统

    这篇文章主要为大家详细介绍了如何基于Java设计一个高并发的秒杀系统,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考下
    2023-10-10
  • mybatis generator 配置 反向生成Entity简单增删改查(推荐)

    mybatis generator 配置 反向生成Entity简单增删改查(推荐)

    这篇文章主要介绍了mybatis generator 配置 反向生成Entity简单增删改查(推荐)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-12-12
  • 两个小例子轻松搞懂 java 中递归与尾递归的优化操作

    两个小例子轻松搞懂 java 中递归与尾递归的优化操作

    这篇文章主要介绍了两个小例子轻松搞懂 java 中递归与尾递归的优化操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09

最新评论