简介
这个专题讲述的是Java安全方面的知识,主要包括:类加载器、字节码校验、安全管理与访问、用户认证、数字签名、代码签名、加密。
Java技术提供了相应的安全机制:
- 设计特性:对数组的边界进行检查、类型转换检查、无指针算法等。
- 访问控制:文件访问、网络访问。
- 代码签名:通过加密算法跟踪代码的创建修改等。
类加载器
类加载器的层次结构
引导类加载器
引导类加载器(启动类加载器)主要加载系统类,通常是从jar文件rt.jar中进行加载,是虚拟机不可分割的一部分,通常用C语言实现。引导类加载器没有对应的ClassLoader对象。例如String.class.getClassLoader 返回结果为null。
rt.jar的目录结构
扩展类加载器
扩展类加载器用于从${JAVA_HOME}\jre\lib\ext目录加载(标准的扩展)或者由系统变量-Djava.ext.dir指定位路径中的类库加载。可以将jar文件放入该目录,这样即使没有任何类路径,扩展类加载器也可以找到其中的类。在Oracle的Java语言实现中扩展类加载器是使用Java实现,是URLClassLoader的实例,标准扩展类可以直接进行使用。
package classLoad;
import java.io.File;
import java.util.Properties;
import java.util.StringTokenizer;
/**
* 扩展类加载器
* author zhangdeheng
*/
public class ExtensionClassLoader {
public static void main(String[] args) {
/*
加载当前系统的所有属性
*/
Properties properties=System.getProperties();
System.out.println("获取当前系统属性key--value");
properties.list(System.out);
getExtensionDir();
}
/**
* 获取一个或多个扩展目录路径
* @return
*/
public static File[] getExtensionDir(){
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}
@Override
public int hashCode() {
return super.hashCode();
}
}
系统类加载器
系统类加载器用于加载应用类。它负责加载系统类路径java -classpath
或-D java.class.path
指定路径下的类库。就是说他在有classpath环境变量或者-classpath命令行选项设置的类路径中的目录里或者是jar/zip文件里查找这些类。在Oracle的Java语言实现中系统类加载器是使用Java实现,是URLClassLoader的实例。
类加载器的加载顺序
类加载器有一种父子关系,加载模式为双亲委派模式。除了顶层的引用类加载器,每个类加载器都有一个父类加载器。类加载器工作原理:当一个类加载器收到一个类加载请求,它会把这个请求委派给它的父类,让父类去加载,若该父类上面还有父类,则继续向上委托,最终委托给引用类加载器,如若父类加载器加载成功则返回,若未成功则交由子类自己加载。例如:当系统类加载器接收到要加载String类的请求,系统类加载器会把这个请求委派给扩展类加载器,而扩展类加载器则要求引用类加载器进行加载,引用类加载器查找并加载rt.jar中的这个类,而无须其他类加载器在做加载。加载顺序流程如下:
编写自己的类加载器
编写自定义的类加载器,使我们在向虚拟机传递字节码之前执行定制的检查,例如我们可以编写一个类加载器,来过滤掉没有标记为”paid for“的类。
下面来介绍下要编写自己的类加载器涉及到的类和方法:
类:ClassLoader 方法:findClass(String className)、loadClass(String name,boolean resolve)
自定义自己的类加载器只需要继承ClassLoader类,同时实现findClass方法即可。下面我们来分析下为什么只需要实现findClass方法就能实现自己的类加载器。
loadClass方法源码分析
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
当类加载请求到来的时候,先在缓存中查找是否存在该类对象,如果存在则直接返回,不存在则将请求委托给父加载器进行加载,若没有父加载器则交给引导类加载器进行加载。若最后引导类加载器也没有找到。则使用findClass方法去加载。但jdk中的findClass方法则没有提供具体的加载逻辑,代码如下:
/**
* Finds the class with the specified <a href="#name">binary name</a>.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class. The default implementation
* throws a <tt>ClassNotFoundException</tt>.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*
* @since 1.2
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
在这个方法中直接抛出了个没有找到类的异常,所以若要处理自己的业务逻辑只需实现该方法即可。但要实现该方法,必须要做到以下几点:
- 为来自本地文件系统或其他来源的类加载字节码。
- 调用ClassLoader超类的defineClass方法,像虚拟机提供字节码。
下面借用《java核心技术》中的一个例子进行讲解下自定义实现自己的类加载器,这个例子是用于加载加密过的类文件,同时为了不与常规的类加载器,我们对加密的类文件使用了不同的扩展名.caesar。
package classLoad.myselfClassLoader;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Method;
/**
* 这个frame 包含两个文本域 类的名称 和解密的密钥
*/
public class ClassLoaderFrame extends JFrame {
private JTextField keyField=new JTextField("3",4);
private JTextField nameField=new JTextField("Calculator",30);
private static final int DEFAULT_WIDTH=300;
private static final int DEFAULT_HEIGHT=200;
public ClassLoaderFrame() throws HeadlessException {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setLayout(new GridBagLayout());
add(new JLabel("Class"), new GBC(0, 0).setAnchor(GBC.EAST));
add(nameField, new GBC(1, 0).setWeight(100, 0).setAnchor(GBC.WEST));
add(new JLabel("Key"), new GBC(0, 1).setAnchor(GBC.EAST));
add(keyField, new GBC(1, 1).setWeight(100, 0).setAnchor(GBC.WEST));
JButton loadButton = new JButton("Load");
add(loadButton, new GBC(0, 2, 2, 1));
loadButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
runClass(nameField.getText(), keyField.getText());
}
});
pack();
}
/**
*
* @param name 类名
* @param key 解密的key
*/
public void runClass(String name, String key)
{
try
{
ClassLoader loader = new CryptoClassLoader(Integer.parseInt(key));
Class<?> c = loader.loadClass(name);
Method m = c.getMethod("main", String[].class);
m.invoke(null, (Object) new String[] {});
}
catch (Throwable e)
{
JOptionPane.showMessageDialog(this, e);
}
}
}
package classLoad.myselfClassLoader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 这个类用来加载加密的类文件
*/
public class CryptoClassLoader extends ClassLoader {
private int key;
/**
*
* @param key 解密的key值
*/
public CryptoClassLoader(int key) {
this.key = key;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classBytes=null;
classBytes=loadClassBytes(name);
Class<?> c1=defineClass(name,classBytes,0,classBytes.length);
if(c1==null){
throw new ClassNotFoundException(name);
}
return c1;
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
/**
* 加载解密的类文件字节
* @param name 类文件名
* @return 返回类文件字节数组
* @throws IOException
*/
private byte[] loadClassBytes(String name) throws IOException{
String cname=name.replace(".","/")+".caesar";
byte[] bytes= Files.readAllBytes(Paths.get(cname));
for (int i = 0; i < bytes.length; i++) {
bytes[i]=(byte) (bytes[i]-key);
}
return bytes;
}
}
package classLoad.myselfClassLoader;
import javax.swing.*;
import java.awt.*;
public class ClassLoaderTest {
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
JFrame frame = new ClassLoaderFrame();
frame.setTitle("ClassLoaderTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
package classLoad.myselfClassLoader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 使用caesar密码进行文件加密
*/
public class Caesar {
public static void main(String[] args) throws Exception{
if(args.length!=3){
System.out.println("USAGE:java classLoader.Caesar in out key");
return;
}
try (FileInputStream in = new FileInputStream(args[0]); FileOutputStream out = new FileOutputStream(args[1])){
int key = Integer.parseInt(args[2]);
int ch;
while ((ch=in.read())!=-1){
byte c=(byte)(ch+key);
out.write(c);
}
}
}
}
package classLoad.myselfClassLoader;
/*
GBC - A convenience class to tame the GridBagLayout
Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.awt.*;
/**
This class simplifies the use of the GridBagConstraints
class.
*/
public class GBC extends GridBagConstraints
{
/**
Constructs a GBC with a given gridx and gridy position and
all other grid bag constraint values set to the default.
@param gridx the gridx position
@param gridy the gridy position
*/
public GBC(int gridx, int gridy)
{
this.gridx = gridx;
this.gridy = gridy;
}
/**
Constructs a GBC with given gridx, gridy, gridwidth, gridheight
and all other grid bag constraint values set to the default.
@param gridx the gridx position
@param gridy the gridy position
@param gridwidth the cell span in x-direction
@param gridheight the cell span in y-direction
*/
public GBC(int gridx, int gridy, int gridwidth, int gridheight)
{
this.gridx = gridx;
this.gridy = gridy;
this.gridwidth = gridwidth;
this.gridheight = gridheight;
}
/**
Sets the anchor.
@param anchor the anchor value
@return this object for further modification
*/
public GBC setAnchor(int anchor)
{
this.anchor = anchor;
return this;
}
/**
Sets the fill direction.
@param fill the fill direction
@return this object for further modification
*/
public GBC setFill(int fill)
{
this.fill = fill;
return this;
}
/**
Sets the cell weights.
@param weightx the cell weight in x-direction
@param weighty the cell weight in y-direction
@return this object for further modification
*/
public GBC setWeight(double weightx, double weighty)
{
this.weightx = weightx;
this.weighty = weighty;
return this;
}
/**
Sets the insets of this cell.
@param distance the spacing to use in all directions
@return this object for further modification
*/
public GBC setInsets(int distance)
{
this.insets = new Insets(distance, distance, distance, distance);
return this;
}
/**
Sets the insets of this cell.
@param top the spacing to use on top
@param left the spacing to use to the left
@param bottom the spacing to use on the bottom
@param right the spacing to use to the right
@return this object for further modification
*/
public GBC setInsets(int top, int left, int bottom, int right)
{
this.insets = new Insets(top, left, bottom, right);
return this;
}
/**
Sets the internal padding
@param ipadx the internal padding in x-direction
@param ipady the internal padding in y-direction
@return this object for further modification
*/
public GBC setIpad(int ipadx, int ipady)
{
this.ipadx = ipadx;
this.ipady = ipady;
return this;
}
}
这段代码主要的核心代码CryptoClassLoader这个类,它对使用”3“加密的文件进行了解密,解密的操作是读取文件的每个字节减去key,在将这些解密的字节使用defineClass方法将一个新的类添加到虚拟机中。
备注:
下节内容字节码校验