基于 Java 2 运行时安全模型的线程协作

本文介绍了Java2的运行时安全模型,该模型基于策略和堆栈授权,允许管理员为应用定制安全策略。文章详细解释了保护域、权限和策略的概念,并通过示例展示了如何在多线程环境下构建安全协作的Java应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 2 的运行时安全模型是基于策略和堆栈授权的安全模型。Java 使用存取控制器(AccessController)为用户主机上的大部分关键资源(如文件,套接字等)提供运行时保护,并且允许管理员为具体的应用定制安全策略文件。本文将讨论在多线程的环境下,如何使用 Java 提供的安全特性构建线程间安全协作的 Java 应用。

在 Java 2 之前的版本,运行时的安全模型使用非常严格受限的沙箱模型(Sandbox)。读者应该熟悉,Java 不受信的 Applet 代码就是基于这个严格受限的沙箱模型来提供运行时的安全检查。沙箱模型的本质是,任何本地运行的代码都是受信的,有完全的权限来存取关键的系统资源。而对于 Applet,则属于不受信的代码,只能访问沙箱范围内有限的资源。当然,您可以通过数字签名的方式配置您的 Applet 为受信的代码,具有同本地代码一样的权限。

从 Java 2 开始,Java 提供了基于策略(Policy)与堆栈授权的运行时安全模型,它是一个更加细粒度的存取控制,易于配置与扩展,其总体的架构如图 1 所示:

图 1. Java 2 安全模型
图 1. Java 2 安全模型

简单来讲,当类由类装载器(Class Loader)载入到 JVM 运行,这些运行时的类会根据 Java Policy 文件的配置,被赋予不同的权限。当这些类要访问某些系统资源(例如打开 Socket、读写文件等),或者执行某些敏感的操作(例如存取密码)时,Java 的安全管理器(ava.lang.SecuirtyManager)的检查权限方法将被调用,检查这些类是否具有必要的权限来执行该操作。

在继续深入讨论之前,我们先来澄清下面的几个概念:

  • 策略,即系统安全策略,由用户或者管理员配置,用来配置执行代码的权限。运行时的 java.security.Policy 对象用来代表该策略文件。
  • 权限,Java 定义了层次结构的权限对象,所有权限对象的根类是 java.security.Permission。权限的定义涉及两个核心属性:目标(Target)与动作 (Action)。例如对于文件相关的权限定义,其目标就是文件或者目录,其动作包括:读,写,删除等。
  • 保护域,保护域可以理解为具有共同的权限集的类的集合。

在 Java 2 里,权限实际上是被赋予保护域的,而不是直接赋给类。权限、保护域和类之间的映射关系如图 2。

图 2. 类,保护域,权限的映射关系
图 2. 类,保护域,权限的映射关系

如图 2 所示,当前运行时堆栈是从 a.class 到 e.class。在运行时堆栈上的每一帧(Stack Frame)都会被 Java 划归为某个保护域(保护域是 Java 根据 Policy 文件配置构建出来的)。Java 的安全管理器在执行权限检查时,会对堆栈上的每个 Stack Frame 做权限检查,当且仅当每个 Stack Frame 被赋予的权限集都暗含(Imply)了所要求的权限时,该操作才被允许执行,否则 java.security.AccessControlException 异常将被抛出,该操作执行失败。

有关 Java 2 安全模型,有几点需要特别说明:

  1. 该模型是基于堆栈授权的,这在多线程的环境下,同样适用。例如当父线程创建了子线程,子线程的执行被看作是父线程执行的继续,所以 Java 的安全管理器在权限检查时,所检查的运行时堆栈,既包括当前子线程的,也包括从父线程那里继承过来的运行时堆栈。这意味着,用户不可能通过线程的创建来获得额外的权限。
  2. Java 的开发者可以使用 AccessController.doPrivileged 来优化权限检查带来的额外性能开销。如图 3 所示,Java 的权限检查将从堆栈的顶部开始,逐一向下,直到碰到 doPrivileged 的方法调用,或者到达堆栈底部为止。使用 doPrivileged 可以避免不必要的栈遍历(Stack Traverse),提高程序的性能。
  3. 在该模型中,有一个特殊的保护域,系统域(System Domain)。所有被 null类装载器所装载的类都被称为系统代码,其自动拥有所有的权限。而且,所有的重要的受保护的外部资源,如文件系统、网络、屏幕、键盘等只能通过系统代码获得。
图 3. doPrivileged Stack Frame
图 3. doPrivileged Stack Frame

接下来,本文会给出一个简单的示例,然后我们根据这个示例,进一步深入,来创建一个线程间安全协作的应用。

示例

我们的示例很简单:客户端调用 LogService 提供的 API,把 Message 写入到磁盘文件。

清单 1. 客户端程序
package sample.permtest.client;
    ……
    public class Client {
        ……
        public static void main(String[] args) {
        //构造消息日志,使用LogService将其写入c:\\paper\\client\\out.tmp文件。
        Message message = new Message("c:\\paper\\client\\out.tmp", 
	    "Hi, this is called from client"+'\n');
        LogService.instance.log(message);
		
        //构造消息日志,使用LogService将其写入c:\\paper\\server\\out.tmp文件。
        message = new Message("c:\\paper\\server\\out.tmp", 
	    "Hi, this is called from client"+'\n');
        LogService.instance.log(message);	
    }
}
清单 2. LogService
package sample.permtest.server;
……
public class LogService {
    ……
	
    public void log(Message message) {
        final String destination = message.getDestination();
        final String info        = message.getInfo();
        FileWriter filewriter = null;
        try
        {
            filewriter = new FileWriter(destination, true);
            filewriter.write(info);
            filewriter.close();
        }
        catch (IOException ioexception)
        {
            ioexception.printStackTrace();
        }
    }
}

如清单 1、2 所示,这就是一个普通的 Java 应用程序。我们把这个程序放在 Java 的安全模型中执行。Client 类放在 client.jar JAR 包里,而 LogService 类放在 server.jar JAR 包里

首先我们使用 keytool 工具来生成我们需要的 keystore 文件,以及需要的数字证书,如清单 3 所示。

清单 3. 生成 keystore 文件及其数字证书
>keytool -genkey -alias client  -keyalg RSA -keystore C:\paper\.keystore 
>keytool -genkey -alias server  -keyalg RSA -keystore C:\paper\.keystore

在清单 3 中,我们生成了 C:\paper\.keystore 文件,使用 RSA 算法生成了别名为 client 与 server 的两个数字证书。(注 : 为方便起见,keystore 与 client,server 证书的密钥都是 111111)

我们使用如清单 4 所示的命令来签名 client.jar 与 server.jar。

清单 4. 签名 JAR 文件
>jarsigner.exe  -keystore C:\paper\.keystore  
    -storepass  111111  c:\paper\client.jar  client
>jarsigner.exe  -keystore C:\paper\.keystore  
    -storepass  111111  c:\paper\server.jar  server

在清单 4 中,我们使用了别名为 client 的数字证书来签名 client.jar 文件,使用别名为 server 的数字证书来签名 server.jar 文件。

使用图形化的工具 policytool.exe 创建清单 5 所示的 Policy 文件。

清单 5. Policy 文件
/* AUTOMATICALLY GENERATED ON Thu May 14 15:40:25 CST 2009*/ 
/* DO NOT EDIT */ 

keystore "file:////C:/paper/.keystore"; 

grant signedBy "client" { 
    permission java.io.FilePermission "c:\\paper\\client\\*","read,write"; 
}; 

grant signedBy "server" { 
    permission java.security.AllPermission; 
};

Policy 文件指出,所有被”client”签名的代码具有读写” c:\\paper\\client\\”目录下所有文件的权限,而所有被”server”签名的代码具有所有的权限。Java 将根据该策略文件按照签名者创建相应的保护域。

一切就绪,我们运行代码,如清单 6 所示。

清单 6. 运行程序
>java -Djava.security.manager 
    -Djava.security.policy=my.policy -classpath client.jar;server.jar   
    sample.permtest.client.Client

有两个运行时选项特别重要,-Djava.security.manager 告诉 JVM 装载 Java 的安全管理器,进行运行时的安全检查,而 -Djava.security.policy 用来指定我们使用的策略文件。

运行的结果如清单 7 所示。

清单 7. 运行结果
Exception in thread "main" java.security.AccessControlException: access denied ( 
java.io.FilePermission c:\paper\server\out.tmp write) 
       at java.security.AccessControlContext.checkPermission(Unknown Source) 
       at java.security.AccessController.checkPermission(Unknown Source) 
       at java.lang.SecurityManager.checkPermission(Unknown Source) 
       at java.lang.SecurityManager.checkWrite(Unknown Source) 
       at java.io.FileOutputStream.<init>(Unknown Source) 
       at java.io.FileOutputStream.<init>(Unknown Source) 
       at java.io.FileWriter.<init>(Unknown Source) 
       at sample.permtest.server.LogService.log(LogService.java:19) 
       at sample.permtest.client.Client.main(Client.java:16)

客户端运行后,第一条消息成功写入 c:\\paper\\client\\out.tmp 文件,而第二条消息由于没有 c:\paper\server\out.tmp 文件的写权限而被拒绝执行。

线程间的安全协作

前一节本文给出的示例,如果放在线程间异步协作的环境里,情况会变得复杂。如图 4 所示。

图 4. 线程的异步协作
图 4. 线程的异步协作

如图 4,在这样的情景下,客户端线程的运行时堆栈完全独立于服务器端的线程,它们之间仅仅通过共享的数据结构消息队列进行异步协作。例如:当客户端线程放入 Message X,而后,服务器端的线程拿到 Message X 进行处理,我们仍然假设 Message X 是希望服务器端线程将消息写入 c:\paper\server\out.tmp 文件。在这个时候,服务程序怎样才能确保客户端具有写入 c:\paper\server\out.tmp 文件的权限?

Java 提供了基于线程协作场景的解决方案,如清单 8 所示:

清单 8. 线程协作版本的 LogService
package sample.permtest.thread.server;
……
public class LogService implements Runnable
{
    ……
    public synchronized void log(Message message)
    {
        //该方法将在客户端线程环境中执行
        //在消息放入队列的时候,我们把客户端线程的执行环境通过 
        //AccessController.getContext() 得到,
        //并及时保存下来。
        message.m_accesscontrolcontext = AccessController.getContext();
        _messageList.add(message);
        notifyAll();
    }
    ……
    //从队列中取出消息,并逐一处理
    public void run()
    {
        while (true)
        {
            Message message = null;
            try
            {
                message = retrieveMessage();
            }
            catch (InterruptedException interruptedexception)
            {
                break;
            }
            final String destination = message.getDestination();
            final String stringMessage = message.getInfo();
            AccessController.doPrivileged
            (
                new PrivilegedAction()
                {
                    public Object run()
                    {
                        FileWriter filewriter = null;
                         try
                        {
                            filewriter = new FileWriter(destination, true);
                            filewriter.write(stringMessage);
                            filewriter.close();
                        }
                        catch (IOException ioexception)
                        {
                            ioexception.printStackTrace();
                        }
                        return null;
                    }
                },
                message.m_accesscontrolcontext 
		//将客户端线程当时的执行环境传入,进行权限检查。
            );
        }
    }
}

消息类的 m_accesscontrolcontext 成员变量是一个 AccessControlContext 对象,它封装的当前线程的执行环境快照,我们可以通过调用 AccessController 的 getContext 方法获得。安全的线程协作工作原理如图 5 所示。

图 5. 线程异步协作权限检查路径
图 5. 线程异步协作权限检查路径

图 5 中的箭头指示了 Java 的安全管理器权限检查的路径,从当前的帧 (Frame) 开始,沿着服务器端线程的运行时堆栈检查,直到碰到了 AccessController.doPrivileged 帧。由于我们在调用 doPrivileged 方法时,传入了 m_accesscontrolcontext,也就是客户端线线程在往消息队列里插入消息时的执行环境,所以 Java 的安全管理器会跳转到该执行环境,沿着客户端插入消息时的执行堆栈逐一检查。

在本节线程版本的 Log 服务实现中,Client 类在 sample.permtest.thread.client 包里,该包被导出为 thread_client.jar JAR 包,而 LogService 在 sample.permtest.thread.server 包里,该包被导出为 thread_server.jar JAR 包。而有关这部分的包签名与上节类似,使用了与上节相同的数字证书。

关于完整的源代码,读者可以在本文后面的资源列表中下载。

小结

本文通过示例,详尽描述了 Java 2 运行时的安全模型特性,以及基于该模型,如何构建安全的线程协作应用。值得一提的是,当您的 Java 应用使用的 Java 2 所提供的运行时安全模型,程序性能的降低是必然的,因为我们已经看到,Java 2 的安全模型是基于堆栈授权的,这意味着,每一次 Java 安全管理器检查权限方法的执行,都会遍历当前运行时行堆栈的所有帧,以确定是否满足权限要求。所以您的设计一定要在安全与性能之间取舍。当然,当您在应用了 Java 的安全模型后,您仍然有机会进行性能的优化,比如使用 doPrivileged 方法去掉不必要的堆栈遍历,更进一步,您可以根据自己应用的特点,通过继承 java.lang. SecurityManager 类,来开发适合自己应用的安全管理器。


原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-rtsecurity/





基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人运动设备中的六轴姿态传感器,它集成了三轴陀螺仪三轴加速度计。这款传感器能够实监测并提供设备的角速度线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp``MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式I2C地址,`getAcceleration()``getGyroscope()`则分别用于获取加速度角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()``getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准滤波,以消除噪声漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客其他开源资源是学习解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值