简介
这个专题讲述的是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方法将一个新的类添加到虚拟机中。
备注:
下节内容字节码校验