安全程序设计:07 面向对象中的编程安全

上传人:夏** 文档编号:573352094 上传时间:2024-08-14 格式:PPT 页数:52 大小:286.50KB
返回 下载 相关 举报
安全程序设计:07 面向对象中的编程安全_第1页
第1页 / 共52页
安全程序设计:07 面向对象中的编程安全_第2页
第2页 / 共52页
安全程序设计:07 面向对象中的编程安全_第3页
第3页 / 共52页
安全程序设计:07 面向对象中的编程安全_第4页
第4页 / 共52页
安全程序设计:07 面向对象中的编程安全_第5页
第5页 / 共52页
点击查看更多>>
资源描述

《安全程序设计:07 面向对象中的编程安全》由会员分享,可在线阅读,更多相关《安全程序设计:07 面向对象中的编程安全(52页珍藏版)》请在金锄头文库上搜索。

1、第七章第七章面向对象中的编程安全面向对象中的编程安全 面向对象(Object Oriented,OO)是最流行的软件开发方法之一,也是当前计算机软件工程界关心的重点,从90年代开始,它就慢慢变成了软件开发方法的主流目前,面向对象的概念和应用,已经不仅仅集中于程序设计和软件开发,而是扩充到计算机应用的其他应用场合,如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域面向对象强调人类在日常的思维逻辑中经常采用的思维方法与原则,其中的重要概念如抽象、分类、继承、聚合、多态等,都和我们的生活息息相关,这也成为面向对象思想流行的原因本章主要介绍面向对象中的安

2、全编程。涉及面向对象,内存的分配与释放,静态成员安全等几个方面7.1 面向对象概述7.1.1 面向对象基本原理面向对象,可以说是当前最流行的软件开发方法之一,目前大多数的高级语言都支持面向对象,该思想也是用日常生活中的现象来模拟软件开发的过程面向对象包括两个方面的范畴: 面向对象方法学;面向对象方法学; 面向对象程序开发技术。面向对象程序开发技术。 其中,面向对象方法学是面向对象程序开发技术的理论基础。从面向对象方法学的理论,可以设计出类似人类思维方式和手段的面向对象程序设计语言,从而延伸出面向对象程序开发技术,使得程序开发过程非常类似于人类的认知通过面向对象的方法学和面向对象的程序开发技术开

3、发出的软件,具有模块化特色突出、可读性强、易维护性强等优点在面向对象的方法学中,基于人类对客观世界的认知规律、思维方式和方法,提取出针对软件开发的如下抽象认识: 客观世界由各种各样的实体组成,各自发挥作用,相互进行通信,这些实体称为对客观世界由各种各样的实体组成,各自发挥作用,相互进行通信,这些实体称为对象;象; 每个对象都保存了各自的内部状态,具有一定的功能动作;由于外界其他对象或者每个对象都保存了各自的内部状态,具有一定的功能动作;由于外界其他对象或者外部环境的影响,对象本身依据通信机制,根据具体情况作出不同的反应;外部环境的影响,对象本身依据通信机制,根据具体情况作出不同的反应; 多个相

4、似对象,如果属性和功能动作性质类似,可以将其划分为一类;类与类之间多个相似对象,如果属性和功能动作性质类似,可以将其划分为一类;类与类之间可以有继承关系;可以有继承关系; 复杂的对象可以由相对简单的对象通过一定的方式组合而成;复杂的对象可以由相对简单的对象通过一定的方式组合而成; 对象之间可以通过各种方法进行通信;等等对象之间可以通过各种方法进行通信;等等总而言之,一系列简单或复杂的对象进行组合,其间的相互作用和通信,构成了各种系统,构成了人们所面对的客观世界7.1.2 面向对象的基本概念不管什么样的语言,只要支持面向对象,实际上是利用了面向对象基本思想,其中,最基本的概念有以下几个:对象对象

5、(Object)。对象构成客观世界的一个个实体,从最。对象构成客观世界的一个个实体,从最简单的字符串到复杂的软件系统等,均可看作对象,广简单的字符串到复杂的软件系统等,均可看作对象,广义上,对象不仅能表示具体的实体,还能表示抽象的规义上,对象不仅能表示具体的实体,还能表示抽象的规则或事件等则或事件等 对象具有两个方面的特点:对象具有两个方面的特点:能够保存一定的状态,由“属性(attribute)”来表达;能执行一定的动作,由“方法(method)”来表达类类(class)。类是用于描述同一类型的对象的一个抽象。类是用于描述同一类型的对象的一个抽象的概念,类中定义了这一类对象所应具有的属性和方

6、的概念,类中定义了这一类对象所应具有的属性和方法。多个对象的抽象成为类,类的具体化就是对象,法。多个对象的抽象成为类,类的具体化就是对象,通常的说法,创建一个对象实际上就是将类实例化通常的说法,创建一个对象实际上就是将类实例化消息和方法。消息是指对象之间进行通信的数据或者消息和方法。消息是指对象之间进行通信的数据或者数据结构。在通信的过程中,一个消息发送给某个对数据结构。在通信的过程中,一个消息发送给某个对象,实际上相当于调用另一个对象的方法,消息可以象,实际上相当于调用另一个对象的方法,消息可以是这个方法的参数是这个方法的参数继承继承(Inheritance)。继承性是指子类自动共享父类属。

7、继承性是指子类自动共享父类属性和方法的机制性和方法的机制继承的思想来源于:在定义和实现一个新的类时,可以将一个已经存在的类作为父类,新类的定义在这个父类的基础之上进行,把这个父类中的属性和方法作为自己的属性和方法,并可加入若干新的属性和方法继承性是面向对象程序设计语言不同于其它语言的重要特点,是其他语言所没有的 在类的继承中,如果子类只继承一个父类的属性和方法,称为单重继承;如果子类继承了多个父类的属性和方法,称为多重继承。有些语言(如C+)支持多重继承,有些语言(如Java)不支持多重继承在软件开发的过程中,类和类之间的继承,对于信息的组织与分类,非常有用,它简化了对象、类的创建,增加了代码

8、的可重用性,使建立的软件具有良好的可扩充性和可维护性多态多态(Polymorphism)。多态,是指不同类型对象,收到同一消。多态,是指不同类型对象,收到同一消息息(调用同一个函数等调用同一个函数等),根据其类型,可以产生不同的结果。在,根据其类型,可以产生不同的结果。在面向对象语言中,一般具有两种形式的多态:面向对象语言中,一般具有两种形式的多态: 静态多态,一般指函数重载; 动态多态,一般利用继承和函数覆盖 有了多态性,不同对象可以以适合自身的方式,去响应相同的消有了多态性,不同对象可以以适合自身的方式,去响应相同的消息,增强了软件的可扩展性和和重用性息,增强了软件的可扩展性和和重用性封装

9、封装(Encapsulation)。封装,保证了软件的每个组成部分具有。封装,保证了软件的每个组成部分具有优良的模块性,通过定义外部接口使模块之间的耦合性达到最优良的模块性,通过定义外部接口使模块之间的耦合性达到最小小在面向对象的语言中,对象是封装的最基本单位,增加了程序结在面向对象的语言中,对象是封装的最基本单位,增加了程序结构的清晰性,防止了程序相互依赖性而带来的变动影响构的清晰性,防止了程序相互依赖性而带来的变动影响7.2 对象内存分配与释放7.2.1 对象分配内存对象分配内存,一般叫做对象的实例化。在分配内存之前,必须已经编写了一个类。假设有以下类: 如Java语言中,声明一个对象的语

10、法是: 但是,对象是引用数据类型,定义一个对象引用,相当于声明一个对象,声明不同于创建,声明对象,只分配了存储地址的存储器位置,还没有为其分配内存。只是在内存中定义了一个名字叫做cus的引用,类似于C+中的指针,此时指向空(null)值,或者说引用为空Customer cus; class Customer private String account; private String password; private String cname; 一般情况下,调用new方法才能创建一个对象。如果声明的对象引用不指向任何对象,这样的引用为“空引用(null reference或null poin

11、ter)”;如果声明的对象引用存储了一个实际对象的地址,则称“引用指向一个对象”给对象分配内存也称为实例化对象,在Java中可以用以下方式来进行:这样,就为对象分配了内存。在内存里面,其基本结构如下: cus里面存储了实际对象的地址, 可以通过cus来访问各个成员。因此,在为对象分配内存时,一定要注意引用是否为nullCustomer cus = new Customer(); 7.2.2 对象内存释放对象的生成比较简单,涉及的安全考虑也不多;与此相对应,对象的内存也有释放的过程,但是和生成相比,它与系统安全性的关系更大一些 现在以C+为例,来阐述对象在内存中的存储和释放情况。对象通常存放在三

12、个内存区域: 1:全局/静态数据区:主要存放全局对象和静态对象,在该内存区的对象或成员,直到进程结束,才会释放内存2:堆:存在于堆中的数据,分配内存的方法一般是:new/malloc,释放内存的方法是:delete/free。对于这种对象,我们可以进行创建和销毁进行精确控制。堆对象在c+中的使用非常广泛,也得到了广泛的应用,不过,用这种方法分配或释放内存,也有一些缺点: 需要程序员手工管理其创建和释放,如果忘记释放的话,可能会造成内存泄露;需要程序员手工管理其创建和释放,如果忘记释放的话,可能会造成内存泄露; 在时间效率和空间效率上,堆对象的没有栈对象高;在时间效率和空间效率上,堆对象的没有栈

13、对象高; 在程序中,如果频繁使用在程序中,如果频繁使用new来创建堆对象或者用来创建堆对象或者用delete来释放堆对象,会造来释放堆对象,会造成大量的内存碎片,内存得不到充分的使用成大量的内存碎片,内存得不到充分的使用3:栈:栈中一般保存的是局部对象或者局部变量,使用栈对象效率较高,程序员无需对其生存周期进行管理C+中,和对象释放内存相关的,一般是析构函数。析构函数的作用是释放对象申请的资源。如代码Customer.cpp 。生成exe文件,运行,效果为: 提示析构函数的调用中,cus1是一个指针,必须经过delete才能让其调用;而另一种情况下,对象生命周期结束时即可调用。 #includ

14、e iostream.h class Customer private:char * name;public:/构造函数Customer(char * name) this-name=name;coutname构造函数被调用“endl;/其他代码/析构函数Customer() coutname析构函数被调用“endl;int main () Customer *cus1=new Customer(cus1);delete(cus1);Customer cus2=new Customer(cus2);因此,析构函数通常由系统自动调用,在以下几种情况下系统会调用析构函数: 全局对象在进程结束时全局

15、对象在进程结束时; 堆中对象进行堆中对象进行delete/free操作时;操作时; 栈中对象生命周期结束时:包括离开作用域、函数正常正常跳出或者抛出栈中对象生命周期结束时:包括离开作用域、函数正常正常跳出或者抛出异常等异常等在使用析构函数时,可以充分利用它的性质进行一些操作,特别对于栈中对象,由于析构函数调用是由系统自动完成的,所以可以利用这一特性,将一些需要随着对象销毁而必须释放的资源封装在析构函数里由系统自动完成销毁或释放,这些工作的典型案例如: 某些资源的释放;某些资源的释放; 多线程解锁;多线程解锁; 关闭文件;等等关闭文件;等等 这样, 利用栈对象的这一特性进行自动管理,可以避免由于

16、编程时的遗漏而忘记进行某种操作在Java语言中,对象的释放相对简单一些。许多方面,Java 类似于 C+。但是,Java 去除了析构函数,取而代之的是finalize()方法finalize()与C+析构函数有什么区别呢?实际上,finalize()是Java为所有类定义的一个特殊的方法,它提供了类似于C+析构函数的一些功能但是,finalize()与C+的析构函数并不完全一样,finalize()方法的调用并不是在对象的作用域结束之后马上进行,而是与Java 的垃圾回收器紧密相关 提示:Java 语言中,创建一个对象时,Java 虚拟机(JVM)为该对象分配内存、调用构造函数并可使用该对象当

17、程序发现对于某个对象没有有效的引用时,JVM 通过垃圾回收器将该对象标记为释放状态垃圾回收器要将一个对象的内存进行释放时,才自动调用该对象的finalize()方法当 Java虚拟机已确定尚未终止的任何线程无法再通过任何方法访问此对象时,由对象的垃圾回收器调用此方法。对于任何给定对象,Java 虚拟机最多只调用一次finalize() 方法finalize()定义于java.lang.Object中,finalize()方法可以被任何类重写,并完成类似析构函数的功能,以配置系统资源或执行其他清除不过,事实上,可以调用System.gc() 方法强制垃圾回收器来释放这些对象的内存如代码P07_0

18、1.java 。 package prj07;class Customer private String account;private String password;private String cname;protected void finalize() throws Throwable System.out.println(finalize(); public class P07_01 public static void main(String args) Customer cus=new Customer();/给cus置空cus=null;/强制垃圾回收System.gc();

19、运行,屏幕打印:finalize()注意,因为垃圾回收工作可能具有一定的延迟,而手工用System.gc()来进行强制垃圾回收又可能被忘记,因为,很多代码因为在这个问题上忽略了,造成了安全隐患如代码P07_02.java。 package prj07;import java.util.ArrayList;class MyObject protected void finalize() throws Throwable System.out.println(finalize(); public class P07_02 public static void main(String args) A

20、rrayList al=new ArrayList();for (int i=1;i2,i+) MyObject obj=new MyObject();al.add(obj);obj=null;System.gc();运行该代码,没有任何的反应。垃圾回收机运行该代码,没有任何的反应。垃圾回收机制也无法调用制也无法调用MyObject的的finalize()方法方法虽然此时所有的虽然此时所有的obj都被置空,但是它们没都被置空,但是它们没有被释放,因为变量有被释放,因为变量al引用了这些对象。所引用了这些对象。所以,在使用这类功能时要特别小心以,在使用这类功能时要特别小心除非将代码改为除非将代码

21、改为P07_03.java package prj07;import java.util.ArrayList;class MyObject protected void finalize() throws Throwable System.out.println(finalize(); public class P07_03public static void main(String args) ArrayList al=new ArrayList();for (int i=1;i2,i+) MyObject obj=new MyObject();al.add(obj);obj=null;al

22、=null;System.gc();综上所述,Java的垃圾回收机制确实可以减小程序员的工作量,对于Java回收机制,可以遵循以下准则: 在不使用某对象时,显式地将此对象赋空,等待垃圾回收;在不使用某对象时,显式地将此对象赋空,等待垃圾回收;遵循谁创建谁释放的原则,让程序更有条理;遵循谁创建谁释放的原则,让程序更有条理; 可以在合适的场景下使用对象池技术以提高系统性能;可以在合适的场景下使用对象池技术以提高系统性能; 尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间;尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间;必要时候,可以调用必要时候,可以调用System.

23、gc()方法强制垃圾回收。方法强制垃圾回收。classA ca=new ClassA();/应用ca对象/确认没有其他使用之后主动将其设置为空ca=null;不过,根据Java语言规范定义,不同的JVM实现者可能使用不同的算法管理垃圾收集器,System.gc()函数不保证JVM的垃圾收集器一定会马上执行但通常来说,特别在一些特定的场合,如实时系统,用户不希望垃圾收集器突然中断应用程序执行而进行垃圾回收,垃圾收集器的执行影响应用程序的性能,此时我们可以调整垃圾收集器的参数,让垃圾收集器能够通过平缓的方式释放内存7.2.3 对象线程安全在很多情况下,对象可能在多线程的环境下运行。一个对象在其生命

24、周期内可以被多个线程访问,实际上是多线程通信的一种方式,此种情况就会出现多种问题,其中最重要的就是多线程环境下对象的状态安全访问以及修改实际上,很多系统软件(如服务器)已经在底层实现了线程安全,因此,隐患主要来源于:程序员不知道该组件对象使用线程来实现,错误地将一些用非线程机制情况下的风格用进去关于对象线程安全,有两个方面的问题: 1:很多框架下都提供了对象被多个线程访问的机制:很多框架下都提供了对象被多个线程访问的机制,当对象可能当对象可能被多个线程来运行时,千万不能在对象中保存和某个线程相关的状被多个线程来运行时,千万不能在对象中保存和某个线程相关的状态态例如:Java EE中的Servl

25、et,其运行模型为:每一个请求实际上就是一个线程,来运行Servlet的某些函数,此时,Servlet中就不宜保存相应的状态数据2:当对象的可能被多个线程来进行操作时,应该考虑同步问题。:当对象的可能被多个线程来进行操作时,应该考虑同步问题。该问题在前面的章节有所讲解,在此省略该问题在前面的章节有所讲解,在此省略7.2.4 对象序列化安全对象序列化是面向对象语言中的重要特性之一,在Java系列和.net系列中,都可以使用一定的手段实现对象序列化一般情况下,对象具有一定的生命周期,随着生成该对象的程序作用域结束而结束但是,有时候,程序可能需要将对象的状态保存下来,或者写入文件,或者写入数据传输流

26、,在需要时再将对象读入之后进行恢复,这里面就需要序列化的工作对象序列化安全对象序列化,就是将对象的状态转换成字节流(当然也可能是字节流以上的一些包装流),在使用的时候,可以通过读取流中的内容,生成相同状态的对象序列化(Serialization)过程的工作一般是:对象将描述自己状态的数据写出到流中,描述自己状态的数据,一般是成员变量,因此,序列化的主要任务是写出对象成员变量的数值特殊情况下,如果对象中,某个成员变量是另一对象的引用,则被引用的对象也要序列化,因此,序列化工作是递归的在很多应用中,对象序列化具有很重要的作用。如数据传输软件中,传输的数据一般是一个对象,这种情况下,该对象应该具备写

27、入流中的能力,也就是说需要被序列化;另外一些情况下,可能需要将对象写入持久化存储(如数据库和文件),也需要进行对象的序列化以Java为例,将对象序列化的方法很简单,满足两个条件即可:将该对象对应的类实现将该对象对应的类实现Serializable接口;接口;该对象的被序列化成员必须是非静态成员变量;该对象的被序列化成员必须是非静态成员变量;其他语言中,序列化过程类似。不过,对象序列化只能保存成员变量的值,其他内容,如成员函数、修饰符等,都不能保存对象序列化有什么安全问题呢?由于对象序列化之后要在网上传输,或者写入数据流,因此,需要关心的问题一般是信息泄密问题对象被序列化时,使用字节数组表示,并

28、且加上了很多控制标记,在一定程度上阻止了对象成员直接被攻击者识别。但是还是不能完全阻止对象内容的泄密,对保存的对象稍加分析,则可获取需要的信息所以,在序列化时,一定要注意不能让敏感信息(如卡号密码)泄密解决该问题的方法有两种: 在将对象实现序列化时,进行一些处理,如加密在将对象实现序列化时,进行一些处理,如加密不要将敏感信息序列化不要将敏感信息序列化可以通过一些手段,不将某些成员序列化如在如在Java中,可以在成员前面加上中,可以在成员前面加上transient关键字,在关键字,在序列化时,系统将会回避这些字段序列化时,系统将会回避这些字段提示:实际上,不将某些信息序列化,还有其他的一些作用,

29、如: 网络之间传递信息时,可以避免一些占用大量网络之间传递信息时,可以避免一些占用大量字节数的对象进行传输,减轻网络压力;字节数的对象进行传输,减轻网络压力; 在对象中可能有一些成员不是简单的变量,而在对象中可能有一些成员不是简单的变量,而是引用类型,但是这些成员引用没有实现序列是引用类型,但是这些成员引用没有实现序列化接口,一般情况下会出现异常,为了避免这化接口,一般情况下会出现异常,为了避免这种异常,可以将这些成员设置为种异常,可以将这些成员设置为“不序列化不序列化”的的7.3 静态成员安全7.3.1 静态成员的机理在类中,数据成员可以分静态成员、非静态成员两种 类中的成员,通常情况下,必

30、须通过它的类的对象访问,但是可以创建这样一个成员,使它的使用完全独立于该类的任何对象,被类的所有对象共用在成员的声明前面加上关键字static(静态)就能创建这样的成员,这种成员叫做静态成员如果一个成员变量被声明为如果一个成员变量被声明为static,就是静态变量,如果一个成员,就是静态变量,如果一个成员方法被声明为方法被声明为static,就是静态方法,它们就能够在它的类的任何,就是静态方法,它们就能够在它的类的任何对象创建之前被访问,而不必引用任何对象对象创建之前被访问,而不必引用任何对象静态成员变量存储在全局数据区,为该类的所有对象共享,不属于任何对象的存储空间,逻辑上所有对象都共享这一

31、存储单元,对静态成员变量的任何的任何操作影响这一存储单元的所有对象如下代码: 调用: Customer cus1 = new Customer(); Customer cus2 = new Customer(); class Customer private String account; private String password; private String cname; public static String bankName; 之后,内存中数据如下所示: 静态成员从属于一个类,不是某对象的一部分,非静态成员可以直接访问类中静态的成员,但是静态成员不可以访问非静态成员。静态成员的

32、优点是:消除传统程序设计方法中的全局变量,为真正实现封装性提供了必要手段。7.3.2 静态成员需要考虑的安全问题由于静态成员的共享性,就必须考虑其数据安全。除了上一节讲到的线程安全以外,还必须考虑对象对其进行访问时的安全性。主要要注意以下几点: 1:静态成员的初始化操作先于对象的实例化而进行,所以在它们:静态成员的初始化操作先于对象的实例化而进行,所以在它们的初始化中不要启动线程,以免造成数据访问的问题。同时静态成的初始化中不要启动线程,以免造成数据访问的问题。同时静态成员的初始化操作中也不应该有依赖关系;员的初始化操作中也不应该有依赖关系; 2:不用静态变量保存某个对象的状态,而应该保存所有

33、对象应该:不用静态变量保存某个对象的状态,而应该保存所有对象应该共有的状态;共有的状态; 3:不用对象来访问静态变量,而用类名来访问静态变量:不用对象来访问静态变量,而用类名来访问静态变量7.3.3 利用单例提高程序性能单例模式适合于一个类只有一个实例的情况,可以起到提高性能的效果。比如比如Windows中的任务管理器,一旦打开如果再次打开,就不会打开新窗口,中的任务管理器,一旦打开如果再次打开,就不会打开新窗口,又如打印缓冲池和文件系统,它们都是单例的例子。又如打印缓冲池和文件系统,它们都是单例的例子。怎样保证一个对象只有第一次使用的时候实例化,以后要使用,就用第一次实例化的那个? 7.3.

34、3 利用单例提高程序性能单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式的要点有三个: 某个类只能有一个实例;某个类只能有一个实例; 它必须自行创建这个实例;它必须自行创建这个实例; 它必须自行向整个系统提供这个实例它必须自行向整个系统提供这个实例以windows“任务管理器”为例,如图所示: 当你打开任务管理器后,如果再次打开任务管理器,就使用前面已经打开的对象。就可以用单例模式来进行模拟。代码如TaskManagerTest.java 。 package prj07;class TaskManager private

35、 static TaskManager tm=new TaskManager();public static TaskManager getInstance() return tm;private TaskManager() System.out.println(对象被生成”);public void show() System.out.println(显示“);public class TaskManagerTestpublic static void main(String args) TaskManager tm1=TaskManager.getInstance();tm1.show()

36、;TaskManager tm2=TaskManager.getInstance();tm2.show();运行,显示效果为: 当然这只有在确信程序不再需要任何多于一个的实例的情况下。通过单例模式可以做到: 确保一个类只有一个实例被建立;确保一个类只有一个实例被建立; 提供一个对对象的全局访问指针,在不影响单例类的客户端的情况下允许将来有多提供一个对对象的全局访问指针,在不影响单例类的客户端的情况下允许将来有多个实例;等等。个实例;等等。单例模式在其他场合,如数据库连接池、共享对象方面,可以起到提高系统性能的作用。小结本章主要介绍面向对象中的安全编程。涉及面向对象、内存的分配与释放、对象序列化安全、静态成员安全等,最后用单例模式阐述了静态成员对提高系统性能所作的贡献。练习1:写一段静态成员不安全的代码。2:对第1题提出解决方案。3:怎样获取序列化对象中的字段信息?4:对象序列化过程中,怎样保证安全?5:在C和Java中怎样回收内存?6:单例模式为什么能提高程序性能?有何劣势?

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号