缓冲区溢出_在Java中使用Google的协议缓冲区

缓冲区溢出

缓冲区溢出

最近发布了有效的Java第三版,我一直对确定此类Java开发书籍的更新感兴趣,该书籍的最新版本仅通过Java 6进行了介绍。 在此版本中,显然存在与Java 7Java 8Java 9密切相关的全新项目,例如第7章(“ Lambda和流”)中的项目42至48,项目9(“建议尝试使用资源”最终尝试”)和第55条(“明智地退还可选方案”)。 我(非常有点)惊讶地意识到, Effective Java第三版中有一个新项目,并不是由Java的新版本专门驱动的,而是由独立于Java版本的软件开发领域的开发驱动的。 第85条(“ Java序列化的首选替代品”)是促使我撰写此介绍性文章的内容,内容涉及将Google的Protocol BuffersJava结合使用

Josh Bloch在“有效Java,第三版”的条款85中以粗体强调了以下两个与Java序列化有关的断言:

  1. 避免序列化攻击的最佳方法是永远不要反序列化任何东西。
  2. 您没有理由在您编写的任何新系统中使用Java序列化。

在概述了Java反序列化的危险并做出了这些大胆的声明之后,Bloch建议Java开发人员使用他所谓的(跨平台的结构化数据表示形式)(以避免在讨论Java时与术语“序列化”相关的混淆)。 Bloch指出,该类别中的领先产品是JSON ( JavaScript对象表示法)和协议缓冲区( protobuf )。 我发现提到协议缓冲区很有趣,因为最近我一直在阅读和使用协议缓冲区。 在线全面介绍了JSON(甚至Java)的用法。 我觉得Java开发人员对协议缓冲区的了解不如对JSON的了解,因此,有必要在Java上使用协议缓冲区。

Google协议缓冲区在其项目页面上被描述为“一种语言中立,平台中立的可扩展机制,用于序列化结构化数据。” 该页面添加了“思考XML,但是更小,更快,更简单”。 尽管协议缓冲区的优点之一是它们支持以可以被多种编程语言使用的方式表示数据,但本文的重点仅在于将协议缓冲区与Java结合使用。

有许多与协议缓冲区相关的有用在线资源,包括主项目页面GitHub protobuf项目页面proto3语言指南(也提供proto2语言指南),协议缓冲区基础:Java教程, Java生成的代码指南Java API(Javadoc)文档,“协议缓冲区”发行页面和“ Maven存储库”页面。 这篇文章中的示例基于协议缓冲区3.5.1

协议缓冲区基础:Java教程概述了将协议缓冲区与Java一起使用的过程。 与使用Java相比,它涵盖了使用Java时要考虑的更多可能性和事情。 第一步是定义独立于语言的协议缓冲区格式。 这是在扩展名为.proto的文本文件中完成的。 对于我的示例,我已经在下一个代码清单中显示的文件album.proto描述了我的协议格式。

原始专辑

syntax = "proto3";

option java_outer_classname = "AlbumProtos";
option java_package = "dustin.examples.protobuf";

message Album
{
  string title = 1;
  repeated string artist = 2;
  int32 release_year = 3;
  repeated string song_title = 4;
}

尽管以上对协议格式的定义很简单,但其中有很多内容。 第一行明确指出我使用的是proto3,而不是未明确指定时使用的默认默认proto2 。 以option开头的两行仅在使用该协议格式生成Java代码时才有意义,它们指示最外层类的名称以及该最外层类的软件包,该类将被生成以供Java应用程序使用此协议格式使用。

“ message”关键字表示此结构(此处称为“专辑”)需要表示。 此构造中有四个字段,其中三个是string格式,一个是整数( int32 )。 在给定消息中,四个字段中的两个字段可以不止一次存在,因为它们用repeated保留字注释。 请注意,我创建此定义时没有考虑Java,除了两个option s,它们指定了根据此格式规范生成Java类的详细信息。

所述album.proto上述未示出的文件需要被“编译”到Java源类文件( AlbumProtos.javadustin.examples.protobuf包),将允许写入和读出协议缓冲器的二进制格式的对应于定义的协议格式。 使用适当的基于操作系统的存档文件中包含的protoc编译器可以完成Java源代码文件的生成。 就我而言,因为我正在Windows 10中运行此示例,所以我下载并解压缩了protoc-3.5.1-win32.zip以便访问该protoc工具。 下一张图片使用命令protoc --proto_path=src --java_out=dist\generated album.proto描述了我对album.proto运行的protoc

对于运行上面的,我有我的album.proto在文件src目录中指出--proto_path和我有一个创建(但空)目录下名为build\generated生成的Java源代码被放置在由按规定--java_out标志。

指定包中生成的类的Java源代码文件AlbumProtos.java有1000行以上,我不会在此处列出生成的类源代码,但是可以在GitHub上找到。 关于此生成的代码,需要注意的几件有趣的事情是缺少导入语句(完全合格的程序包名称代替了所有类引用)。 Java生成的代码指南中提供了有关由protoc生成的Java源代码的更多详细信息。 重要的是要注意,这个生成的类AlbumProtos仍然不受我自己的任何Java应用程序代码的影响,并且仅由文章前面显示的album.proto文本文件生成。

有了可用于AlbumProtos的生成的Java源代码,我现在将在其中生成该类的目录添加到IDE的源路径中,因为现在将其视为源代码文件。 我也可以将其编译为.class.jar用作库。 现在,在我的源路径中有了这个生成的Java源代码文件,我可以将其与自己的代码一起构建。

在继续本示例之前,我们需要一个简单的Java类来用Protocol Buffers表示。 为此,我将使用下一个代码清单(也在GitHub上提供)中定义的Album类。

相册

package dustin.examples.protobuf;

import java.util.ArrayList;
import java.util.List;

/**
 * Music album.
 */
public class Album
{
   private final String title;

   private final List<String> artists;

   private final int releaseYear;

   private final List<String> songsTitles;

   private Album(final String newTitle, final List<String> newArtists,
                 final int newYear, final List<String> newSongsTitles)
   {
      title = newTitle;
      artists = newArtists;
      releaseYear = newYear;
      songsTitles = newSongsTitles;
   }

   public String getTitle()
   {
      return title;
   }

   public List<String> getArtists()
   {
      return artists;
   }

   public int getReleaseYear()
   {
      return releaseYear;
   }

   public List<String> getSongsTitles()
   {
      return songsTitles;
   }

   @Override
   public String toString()
   {
      return "'" + title + "' (" + releaseYear + ") by " + artists + " features songs " + songsTitles;
   }

   /**
    * Builder class for instantiating an instance of
    * enclosing Album class.
    */
   public static class Builder
   {
      private String title;
      private ArrayList<String> artists = new ArrayList<>();
      private int releaseYear;
      private ArrayList<String> songsTitles = new ArrayList<>();

      public Builder(final String newTitle, final int newReleaseYear)
      {
         title = newTitle;
         releaseYear = newReleaseYear;
      }

      public Builder songTitle(final String newSongTitle)
      {
         songsTitles.add(newSongTitle);
         return this;
      }

      public Builder songsTitles(final List<String> newSongsTitles)
      {
         songsTitles.addAll(newSongsTitles);
         return this;
      }

      public Builder artist(final String newArtist)
      {
         artists.add(newArtist);
         return this;
      }

      public Builder artists(final List<String> newArtists)
      {
         artists.addAll(newArtists);
         return this;
      }

      public Album build()
      {
         return new Album(title, artists, releaseYear, songsTitles);
      }
   }
}

在定义了Java“数据”类(“ Album )并使用协议缓冲区生成的Java类(可以表示该专辑)的情况下( AlbumProtos.java ),我准备编写Java应用程序代码来“序列化”专辑信息,而无需使用Java序列化。 该应用程序(演示)代码位于GitHub上可用AlbumDemo类中, 在本文中,我将着重AlbumDemo该类。

我们需要生成一个用于示例的Album实例,并通过下一个硬编码列表来完成。

生成Album样本实例

/**
 * Generates instance of Album to be used in demonstration.
 *
 * @return Instance of Album to be used in demonstration.
 */
public Album generateAlbum()
{
   return new Album.Builder("Songs from the Big Chair", 1985)
      .artist("Tears For Fears")
      .songTitle("Shout")
      .songTitle("The Working Hour")
      .songTitle("Everybody Wants to Rule the World")
      .songTitle("Mothers Talk")
      .songTitle("I Believe")
      .songTitle("Broken")
      .songTitle("Head Over Heels")
      .songTitle("Listen")
      .build();
}

Protocol Buffers生成的类AlbumProtos类包含一个嵌套的AlbumProtos.Album类,我将使用该类以二进制形式存储我的Album实例的内容。 下一个代码清单演示了如何完成此操作。

Album实例化AlbumProtos.Album

final Album album = instance.generateAlbum();
final AlbumProtos.Album albumMessage
   = AlbumProtos.Album.newBuilder()
      .setTitle(album.getTitle())
      .addAllArtist(album.getArtists())
      .setReleaseYear(album.getReleaseYear())
      .addAllSongTitle(album.getSongsTitles())
      .build();

如前面的代码清单所示,“生成器”用于填充协议缓冲区生成的类的不可变实例。 参照该实例,我现在可以使用该实例上的toByteArray()方法轻松地以Protocol Buffers二进制格式写出该实例的内容,如下面的代码清单所示。

写作AlbumProtos.Album二进制形式

final byte[] binaryAlbum = albumMessage.toByteArray();

如下面的代码清单所示,可以完成将byte[]数组读回Album实例的操作。

AlbumProtos.Album二进制形式实例化Album

/**
 * Generates an instance of Album based on the provided
 * bytes array.
 *
 * @param binaryAlbum Bytes array that should represent an
 *    AlbumProtos.Album based on Google Protocol Buffers
 *    binary format.
 * @return Instance of Album based on the provided binary form
 *    of an Album; may be {@code null} if an error is encountered
 *    while trying to process the provided binary data.
 */
public Album instantiateAlbumFromBinary(final byte[] binaryAlbum)
{
   Album album = null;
   try
   {
      final AlbumProtos.Album copiedAlbumProtos = AlbumProtos.Album.parseFrom(binaryAlbum);
      final List<String> copiedArtists = copiedAlbumProtos.getArtistList();
      final List<String> copiedSongsTitles = copiedAlbumProtos.getSongTitleList();
      album = new Album.Builder(
         copiedAlbumProtos.getTitle(), copiedAlbumProtos.getReleaseYear())
         .artists(copiedArtists)
         .songsTitles(copiedSongsTitles)
         .build();
   }
   catch (InvalidProtocolBufferException ipbe)
   {
      out.println("ERROR: Unable to instantiate AlbumProtos.Album instance from provided binary data - "
         + ipbe);
   }
   return album;
}

如最后一个代码清单所示,在调用生成的类中定义的static方法parseFrom(byte[])过程中,可能引发检查异常InvalidProtocolBufferException 。 获得生成的类的“反序列化”实例本质上是一行,其余几行从生成的类的实例中获取数据,并将该数据设置在原始Album类的实例中。

演示类包括两行,这些行打印出原始Album实例的内容,以及最终从二进制表示形式检索到的实例。 这两行包括对两个实例的System.identityHashCode()调用,以证明即使内容匹配,它们也不是同一实例。 当使用前面显示的硬编码的Album实例详细信息执行此代码时,输​​出如下所示:

BEFORE Album (1323165413): 'Songs from the Big Chair' (1985) by [Tears For Fears] features songs [Shout, The Working Hour, Everybody Wants to Rule the World, Mothers Talk, I Believe, Broken, Head Over Heels, Listen]
 AFTER Album (1880587981): 'Songs from the Big Chair' (1985) by [Tears For Fears] features songs [Shout, The Working Hour, Everybody Wants to Rule the World, Mothers Talk, I Believe, Broken, Head Over Heels, Listen]

从此输出中,我们看到两个实例中的相关字段相同,并且两个实例确实是唯一的。 与使用Java的实现序列化接口的“近乎自动”序列化机制相比,这需要付出更多的努力,但是与这种方法相关联的重要优势可以证明成本合理。 Josh Bloch在有效的Java第三版中讨论了Java默认机制中与反序列化相关的安全漏洞,并断言“没有理由在编写的任何新系统中使用Java序列化。

翻译自: https://www.javacodegeeks.com/2018/01/using-googles-protocol-buffers-java.html

缓冲区溢出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值