Java资源访问的安全控制
在JDK的API中经常会看到SecurityManager
的应用,例如System.getProperty
:
public static String getProperty(String key) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertyAccess(key);
}
return props.getProperty(key);
}
不了解Java安全体系的话,可能并不知道中间几行代码的具体作用。本文先简单介绍下Java安全体系中的资源访问控制机制。
1. 资源访问控制
要控制Java程序的资源访问权限,主要有两点:
一是,定义访问策略,即java policy文件,简单的说就是资源访问授权,默认的policy文件为$JAVA_HOME/jre/lib/security/java.policy
,内容如下:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on un-privileged ports
permission java.net.SocketPermission "localhost:1024-", "listen";
// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};
授予JDK的ext目录下代码全部访问权限,授予其余任意代码的一系列默认权限。
使用Java自带的policytool工具可以创建Policy文件,具体参考Policy File Creation and Management
二是,启动SecurityManager
,默认情况下Java是不启动安全检查的。运行Java程序需带上-Djava.security.manager
参数即可启动安全校验,例如:
java -Djava.security.manager SecurityTest
只此两步即可开启资源访问的授权控制。
2. 访问控制示例
编写一段代码,完成读取系统属性、读文件、写文件三个操作,示例如下:
public static void main(String[] args) throws Exception {
// properties
System.out.println("properties:");
String[] properties = { "os.name", "java.version", "java.home", "user.home", "java.ext.dirs" };
for (String prop : properties) {
String value = System.getProperty(prop);
System.out.println(prop + " = " + value);
}
// read file
System.out.println();
System.out.println("read file:");
String file = "E:\\download\\col_iplib.txt";
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line = reader.readLine();
System.out.println("read line: " + line);
reader.close();
// write file
System.out.println();
System.out.println("write file:");
file = "E:\\download\\security-test.txt";
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
line = "test java security";
writer.write(line);
writer.newLine();
System.out.println("write line: " + line);
writer.close();
}
在不开启安全检查时,可以正常运行,结果如下:
E:\workspace\proj\alg\target\classes>java com.github.myron.security.SecurityTest
properties:
os.name = Windows 7
java.version = 1.8.0_40
java.home = C:\Program Files\Java\jre1.8.0_40
user.home = C:\Users\gengmaozhang01
java.ext.dirs = C:\Program Files\Java\jre1.8.0_40\lib\ext;C:\windows\Sun\Java\lib\ext
read file:
read line: 1.0.1.0|1.0.3.255:CN|福建|None|None|CHINANET|None|None
write file:
write line: test java security
当开启安全检查时,即会报错。
E:\workspace\proj\alg\target\classes>java -Djava.security.manager com.github.myron.security.SecurityTest
properties:
os.name = Windows 7
java.version = 1.8.0_40
Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.home" "read")
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.checkPropertyAccess(Unknown Source)
at java.lang.System.getProperty(Unknown Source)
at com.github.myron.security.SecurityTest.main(SecurityTest.java:30)
因为Java默认的java.policy文件并未授予所有程序读取java.home的权限,及文件读写权限。
使用policytool工具创建自己的policy文件,授予上述系统属性、文件读写的权限,如下:
生成的policy文件如下:
/* AUTOMATICALLY GENERATED ON Sun Oct 23 20:30:42 CST 2016*/
/* DO NOT EDIT */
grant codeBase "file:/E:/workspace/proj/alg/target/classes/*" {
permission java.io.FilePermission "<<ALL FILES>>", "read, write";
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "user.home", "write, read";
permission java.util.PropertyPermission "java.ext.dirs", "read";
};
使用-Djava.security.policy
参数指定该policy策略,执行正常:
E:\workspace\proj\alg\target\classes>java -Djava.security.manager -Djava.security.policy=E:/download/my.java.policy com.github.myron.security.SecurityTest
properties:
os.name = Windows 7
java.version = 1.8.0_40
java.home = C:\Program Files\Java\jre1.8.0_40
user.home = C:\Users\gengmaozhang01
java.ext.dirs = C:\Program Files\Java\jre1.8.0_40\lib\ext;C:\windows\Sun\Java\lib\ext
read file:
read line: 1.0.1.0|1.0.3.255:CN|福建|None|None|CHINANET|None|None
write file:
write line: test java security
3. 自定义权限控制
在具体项目中,除了常见的系统资源,还有其他业务资源需要进行访问控制,这里就以消息服务为例来演示自定义的权限控制。
首先,定义自己的资源权限,如下:
public class MessagePermission extends BasicPermission {
private static final long serialVersionUID = -6179037573716029507L;
public static final String MESSAGE_READ_ACTION = "readMessage";
public static final String MESSAGE_WRITE_ACTION = "writeMessage";
private String actions;
public MessagePermission(String name) {
super(name);
}
public MessagePermission(String name, String actions) {
// NOTE: actions is ignored by BasicPermission
super(name, actions);
this.actions = actions;
}
@Override
public String getActions() {
return actions;
}
// 简单的Actions比较实现,更精细的实现可参考PropertyPermission
@Override
public boolean implies(Permission p) {
if (super.implies(p)) {
MessagePermission mp = (MessagePermission) p;
if (mp.getActions() == null || mp.getActions().isEmpty()) {
return true;
}
if (this.actions == null || this.actions.isEmpty()) {
return false;
}
if (this.actions.contains(mp.getActions())) {
return true;
}
return false;
}
return false;
}
}
然后,编写有安全校验的消息读写程序,代码如下:
public class SecurityMessageService {
public static final String MESSAGE_NAME = "SecurityMessage";
public void readMessage() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new MessagePermission(MESSAGE_NAME, MessagePermission.MESSAGE_READ_ACTION));
}
// do something
System.out.println("read message done");
}
public void writeMessage() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new MessagePermission(MESSAGE_NAME, MessagePermission.MESSAGE_WRITE_ACTION));
}
// do something
System.out.println("write message done");
}
public static void main(String[] args) {
SecurityMessageService messageService = new SecurityMessageService();
// read
messageService.readMessage();
// write
messageService.writeMessage();
}
}
直接开启安全检查执行,抛出了无消息读写权限的异常:
E:\workspace\proj\alg\target\classes>java -Djava.security.manager com.github.myron.security.SecurityMessageService
Exception in thread "main" java.security.AccessControlException: access denied ("com.github.myron.security.MessagePermis
sion" "SecurityMessage" "readMessage")
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at com.github.myron.security.SecurityMessageService.readMessage(SecurityMessageService.java:17)
at com.github.myron.security.SecurityMessageService.main(SecurityMessageService.java:36)
最后,创建policy策略文件,授予消息读写权限,如下:
/* AUTOMATICALLY GENERATED ON Sun Oct 23 22:17:00 CST 2016*/
/* DO NOT EDIT */
grant codeBase "file:/E:/workspace/proj/alg/target/classes/*" {
permission com.github.myron.security.MessagePermission "SecurityMessage", "readMessage,writeMessage";
};
执行正常完成,结果如下:
E:\workspace\proj\alg\target\classes>java -Djava.security.manager -Djava.security.policy=E:/download/message.policy com.github.myron.security.SecurityMessageService
read message done
write message done
参考:
1. Trail: Security Features in Java SE
2. 自定义java.policy配置
3. Java程序访问权限控制(policy文件)