如何才能使用户可以访问您的应用程序数据,又不会将您的实体 bean 直接暴露给 Web 层,从而不会使您的应用程序面临安全性威胁?
Brett McLaughlin 提供了一个解决方案,它可使您的实体 bean 很安全,并且使您的整个应用程序有效率地运行。
Enterprise JavaBeans 技术一般分成三种核心类型的 bean:会话 bean、消息驱动 bean 和实体 bean。bean 还可以分成充当业务对象的 bean 和充当数据对象的 bean。会话 bean 和消息驱动 bean 是业务对象;实体 bean 是数据对象。大多数情况下,只需要将业务对象暴露给 Web 层(有时称为应用层),因为业务对象可以使用数据对象来处理数据存储。但在有些情况下,有必要允许用户直接访问和操作数据对象,这意味着要将实体 bean 暴露给 Web 层。这会使您的应用程序面临安全性威胁,而且随着您的应用程序的发展,还会导致杂乱无章的代码。
在这篇EJB最佳实践的文章中,我们将讨论为什么绝对不可以将实体 bean 暴露给应用程序的 Web 层,以及为什么这是难以避免的。接着我将向您演示一个变通方法,它既能使您的实体 bean 更安全,又能使您的整个应用程序更有效率。
暴露的风险
实体bean揭示了大量有关数据库底层结构的信息。因为每个bean都包含用于数据库中的各个字段的取值(accessor)和赋值(mutator)方法(即,getxxx() 和 setxxx()),而且因为方法名称通常与字段名称连在一起(User bean 中的 getFirstName() 通常访问 Users表中的firstName字段),所以从应用程序的实体bean推断这个应用程序的整个数据库结构是可能的。尽管在单个应用程序中这可能不是什么大问题,但您的bean常常会暴露给其它网络上另外的应用程序。在 Web 服务系统中尤其会发生这种情况,其中应用程序都是跨多个网络链接的。
暴露的 bean:一个工作示例
请考虑称为Movie的实体bean。该bean的方法是getTitle()、getDirector()和getActors()。因为您已暴露了该 bean 的远程接口,所以您的应用程序和几个客户机应用程序都编写了合并 getDirector() 方法的代码。但是,随着应用程序的发展,它应该能接受由多位导演指导的电影,这一点很清楚。数据库更改了,您的 getDirector() 方法变成了 getDirectors()。现在该方法是复数形式并被编码为返回一个列表,而不是单一对象。其结果是,所有使用旧方法的代码都将破坏,从而不得不重写您的应用程序代码(以及您的客户机应用程序代码)。
除了存在使您数据库结构暴露给未知应用程序的明显危险之外,您还应该考虑当数据结构更改时会发生什么。因为实体 bean 与您的数据结构紧密地结合在一起,所以随着数据库表中列的更改或新列的添加,实体 bean 常常要随之更改。通过将您的实体 bean 暴露给应用层,可以使它们的方法名称可用,而且这些方法名称几乎总是被您的应用程序和外部应用程序合并到新的应用程序代码中。随着您的实体 bean 的更改以及发展,就会出现严重的代码混乱。
会话虚包模式
幸运的是,有一个设计模式让您允许用户访问数据对象,同时又不会将实体 bean 暴露给 Web 层。会话虚包(Session Facade)模式在实体 bean 和应用程序客户机之间放置了一个会话 bean。当我通过这种方法使用会话 bean 时,我喜欢把它当作管理器,因为它的任务是管理对其它实体的访问。
会话bean旨在充当业务逻辑和应用逻辑之间的接口。在我们的工作示例的例子中,逻辑极其简单,而且它只调出一个底层实体 bean。然而在实际情况中,您可能会选择让会话 bean 管理数据验证、安全性或任何其它特定于业务的功能。通过将实体 bean 封装在会话 bean 中,您可以访问所有所需的业务功能,同时不会“污染”您的实体 bean 代码。
为了解决工作示例中的问题,您可以创建称为 MovieManager 的会话 bean。MovieManager bean 将包含旧方法 getDirector() 和新方法 getDirectors()。当添加了新方法时,它只是被“代理”到您的实体 bean。因为第一个方法在实体 bean 中不再可用,所以会话 bean 使用助手方法来隐藏它,如清单 1 所示:
清单 1. 隐藏已不使用的方法
public Person getDirector() {
// We can't call getDirector() any more on the entity bean
// Call the new method, through this manager
List directors = getDirectors();
// Return the first one in the list
return (Person)directors.item(0);
}
清单 1 的适用性不是特别健壮,但它获得了成功。因为隐藏了旧方法(而不是删除),所以所有与它相关的应用程序代码都一直存在。通过将助手方法放置在它所属的业务对象中,您还将它排斥在实体 bean 之外。解决了这个紧急的问题后,您可以选择将 bean 的客户机手工移植到新方法,或让助手方法一直处理移植工作。不管是哪种方法,都不会影响您的应用程序代码和您的客户机代码。
隐藏数据结构
尽管上面的解决方案确实解决了更改管理的问题,但它不能消除安全性问题。您仍需要保护实体bean(并由此保护您的数据结构),以免暴露给Web层。通过向会话bean添加一些简单的业务逻辑和数据操作功能,您不仅可以隐藏应用程序的数据结构,还可以提供对它所包含的信息的更复杂访问。
例如,随着应用程序的发展,您可能发现让用户依次访问各种数据对象(例如,导演、制片人、演员)的效率很低。因为这类信息几乎总是被放在一起,并一起使用,所以您可能发现重新确定会话 bean 的用途会很有帮助。您的会话 bean 将不包含 getDirector() 或 getDirectors() 方法,而是包含了如清单 2 所示的新的业务逻辑:
清单 2. 用会话 bean 掩盖数据结构
public List getCrew(String movieName)
throws NamingException, RemoteException {
List crew = new LinkedList();
EJBHomeFactory f = EJBHomeFactory.getInstance();
MovieHome movieHome =
(MovieHome)f.lookup("java:comp/env/ejb/Movie", MovieHome.class);
Movie movie = movieHome.findByName(movieName);
crew.add(movie.getDirectors());
crew.add(movie.getProducers());
crew.add(movie.getExecutiveProducers());
// and so on...
return crew;
}
public List getCast(String movieName)
throws NamingException, RemoteException {
List cast = new LinkedList();
EJBHomeFactory f = EJBHomeFactory.getInstance();
MovieHome movieHome =
(MovieHome)f.lookup("java:comp/env/ejb/Movie", MovieHome.class);
Movie movie = movieHome.findByName(movieName);
crew.add(movie.getActors());
crew.add(movie.getStandIns());
// and so on...
return cast;
}
通过分离不同的应用程序层,以及使用业务逻辑来处理数据操作,您既阻止了对实体 bean 直接而且可能不安全的访问,又为您的 Web 层创建了更有意义的方法集。本例中,会话虚包充当了实体 bean 的封装器以及真正的业务逻辑单元,从而将原始数据转变成有意义的信息。
会话虚包模式是许多其它设计模式的基本构件,其优点远远不止这里所讨论的。在 EJB 最佳实践的下一篇专栏文章中,我们将使用本系列的第一篇技巧文章中研究的业务接口(Business Interface)模式,以及您在这里了解到的某些诀窍来进一步抽象出使用您应用程序中所有类型 EJB 组件的过程。
要避免的解决方案
解决所描述的这类问题的一个常见解决方案是简单地将助手方法添加到实体bean本身。但这样做的后果可能是灾难性的。即使您实体bean中的每个方法每年只更改一次,但bean会在那年结束时将拥有双倍数量的方法。当您考虑方法不只是每年更改一次,而是以频繁得多的频率更改时,为什么这个“邦迪式(Band-Aid)”方法不起作用就很明显了。当实体bean看上去不再象数据对象而开始象助手类时,您知道您已经有问题了。另一方面,会话 bean 常常看上去十分象助手类,因为那是会话 bean 旨在实现的功能之一。