Java安全--类加载器

简介

这个专题讲述的是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);
    }

在这个方法中直接抛出了个没有找到类的异常,所以若要处理自己的业务逻辑只需实现该方法即可。但要实现该方法,必须要做到以下几点:

  1. 为来自本地文件系统或其他来源的类加载字节码。
  2. 调用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方法将一个新的类添加到虚拟机中。


备注:

参考博客

下节内容字节码校验



猜你喜欢

转载自blog.csdn.net/qq_18377515/article/details/79505290