Security1 1(一)|学习笔记

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 快速学习 Security1 1(一)

开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Security1 1】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址:https://developer.aliyun.com/learning/course/75/detail/15823


Security1 1(一)

 

内容介绍:

一、Java 的类加载器

二、 字节码的校验        

三、安全管理器和权限

  

一、Java的类加载器

本节课介绍 Java 的安全体系,内容比较多会分为两次进行,所以是Security 1和Security 2。

本节课从最简单的 Java 的类加载器进行讲解,其次将字节码的校验,安全管理器和权限以及用户的认证。这些东西和在大二里面使用 username/pwd 将他们存到数据库中在前端进行校验不是一个概念。它涉及的范围会更广,在大二的时候涉及的只是用户有没有权限登录,可以存储到数据库中,在数据库里密码可以进行设置进行控制。现在所讲的更复杂一些,包括了用户的认证授权等等。先从最简单的进行即 java 的类加载器,Java 类加载器实际在JVM规范里面有规定,关于JVM规范很复杂,比 Java 语言本身的规范还长。Java 虚拟机的启动,首先有 Bookstrap class loader,所有的类加载器在 Java 里面需要 Java 虚拟机运行所有的东西,这里会有疑问,Java 虚拟机在一开始启动的时候,必然会有一个不需要 Java 虚拟机的东西才能运行,这个问题很像鸡生蛋、蛋生鸡的问题,不能说 Java 虚拟机要运行必须要有一个 Java 虚拟机加载,需要打破死循环。所以在整个 Java 虚拟机的一开始有一个Bookstrap类加载器,他可能是 c++写的,也可能是c写的,需要看Java虚拟机的版本。所以 Bookstrap 不需要 Java 虚拟机本身,Bookstrap 启动之后所有的东西都是由它加载。加载的第一个是rt.jar,就是在使用 Java 语言的时候很多不需要声明就可以直接使用的,即不需要 import 就可以直接使用,不需要拿到其他的夹包导入,像 merely 里面需要使用 spring 等东西,还需要找到 spring 的夹包。rt.jar是 jre 环境里自带的夹包,由 Bookstrap 加载,加载之后像strain之类的东西可以直接使用。Bookstrap 会去加载扩展类加载器,扩载类加载器加载运行环境里面 lib 目录的 ext,此目录下的包。比如应用想要访问数据库可以将数据库的驱动放到jre/lib/ext里面,这样就不需要将数据库驱动放到工程里。因为如果放到工程里面每一个工程都需要 ext,如果放到当前的服务器jre/lib/ext 里面实际上启动时就会自动加载进来。扩展类加载器会加载系统类加载器,系统类加载器在 CLASSPATH 里面去找相应的类加载。意思是比如想要 load 一个类,叫做 mypackage.Myclass,假设现在运行在 Tomcat 里,Tomcat 本身是由系统类加载器加载的。现在不叫系统类加载器叫做应用类加载器。Tomcat 在拿到部署包比如plugin.jar的时候会创建一个新的类加载器即 plugin class loader。比如在 Tomcat 的 webapp 下面加载的应用不止一个比如A、B,这时会新创建一个类加载器加载A,再创建一个新的加载B,A、B之间是隔离的。假设创建了一个 pluginloader 类加载器,创建APP使用 pluginloader 类加载器加载,加载plugin.jar,于是plugin 里面 myclass 类文件会加载到 plugin class loader 类加载器中。这就是加载的过程。现在需要了解加载过程会有什么问题,现在可以看到 myclass 的类被加载到 plugin class loader,假设还有其他应用,比如A应用也想要访问 myclass 的类,加载A应用的类加载器会在自己的类加载器中去找有没有此应用 myclass.class 文件,一个类加载器可以加载很多类,显然应用中没有加载过,于是便会去询问系统类加载器,询问他有没有,系统类加载器就会找 myclass,发现并没有加载过。于是会去询问扩展类加载器,扩展类加载器里面没有会去在类加载器里找。都没有找到就会在 CLASSPATH 路径里找,反馈结果是找不到,找不到A,可以自己加载 myclass.class 文件,A可能会找不到 myclass.class 夹包,没有给过夹包,是在plugin.jar里面给的。当A找不到 myclass.class 的定义时,不会去问兄弟节点,会去问父亲,一直问到根,问到根之后再找不到,去 CLASSPATH 找,再找不到就没有。所以类加载器可以做名词空间,加载A应用的加载器可以和加载plugin class loader 之前是完全隔离的。同样A和B之间隔离。这种隔离的意义是假设A应用里面用到 MySQL5,所以对应的有MySQL5 的 driver,B应用里面用到 MySQL8,所以对应的有 MySQL8 的 driver,两个 driver 都打到了自己的袜包里。B类加载器里面有8版本的 MySQL driver,A里面有5版本的 MySQL driver。但是AB之间因为类加载器可以当名词空间用,因此两者之间完全隔离,互相不干扰。所以在 Tomcat 里面既可以有8版本的MySQL 驱动,也可以有5版本的 MySQL  驱动,这就是 Tomcat 在加载应用的时候必须要给每一个应用弄一个单独的类加载器加载。所以类加载器的好处是可以做名次空间,可以做隔离每个应用之间不会彼此干扰。假设将driver直接放到 jre/lib/ext,比如 jre/lib/ext里面已经放了 MySQL8 的驱动,问题是不会产生。刚刚已经讲解A如果想要访问 MySQL 回去找 driver,现在自身的类加载器没有去找,一找就会找到5,不会在去上面回溯。只有在当前的类加载器找不到driver类的定义时才会继续向上运行。所以即便在 jre/lib/ext 加载过,A也不会有任何问题,前提是 CLASSPATH 是如何定义的,CLASSPATH 如果定义的第一个目录是当前目录,第二个目录是其他的目录就不会有任何问题,因为总是在当前的类加载器找,这便是类加载器的工作原理。

image.png

跑了两个应用,比如两个小程序,两个小程序分别使用不同的 class loader  加载,所以是分离的就像之前A和B的关系。在1里面加载了 Banner.class,在加载的小程序2里面也加载 Banner.class,可以看到一个是v1版本,一个是v2版本,二者没有任何干扰。

1加载的是v1版本,访问 Banner 的时候会在自己的类加载器找到Banner.class,不会找到2,因为在兄弟节点之间类加载器是隔离的,所以会产生这样的效果。

可以看到在执行 class loader for applet#1程序的时候,在当前的线程里设置类加载器,创建一个类加载器,通过线程上的上下文环境 Context,获取当前线程的类加载器,与它关联的类加载器,用 loader.loadClass(className)类加载器加载类,这样的话多个线程彼此之间不干扰。大的前提是整个Java虚拟机是一个单独的进程,所有的东西都在里面整个的是一个单独的进程,所以写到 class loader for applet#1 的代码就会知道不同的线程之间有不同的类加载器,所以在加载类的时候,彼此隔离不会有错,即使两个 banner 的类的定义不同,也不会产生任何干扰。

image.png

接下来就会有问题,类加载器加载的是什么,加载的是 class 文件,class 文件是字节码,字节码最大的问题是如果用 Java 的反编译工具去反编译字节码就会得到源码,也就是写的所有代码即使是 Java代码即使是编译过的也可以反编译出来源码,反编译出来的源码和之前的源码有一点差异。比如原来的方法叫做  SetAge(int age),当它反编译出来之后可能叫做  set 后面为一连串数字(int....)即标识会变,因为在编译的时候,无论是方法名还是变量都会被翻译成地址,所以在反编译时,编译回来已经不知道叫什么,只把地址显示出来,做的比较好的反编译器会将地址用助记图显示出来,但是与原来的可能不太一样。不管怎样说源码容易泄露,所以就容易出错,于是对字节码做混淆处理,混淆实际上就是一种加密。这时写一个自己的类加载器试一试如何做混淆,如果要写类加载器,扩展 ClassLoader 就可以,要有一个构造器 CryptoClassLoader(int K),int K 参数是后面要用的,即密钥。做一个混淆就是所有的字节在写入到文件里面之前全部要加一个偏译量,也就是在上面全都加一个数字,k就是设置的偏译量,在这个类里面有一些方法需要设置,边界也要覆盖到。第一个是 findClass,findClass 是 ClassLoader 里面的方法,需要将类加载,所以需要传递名字,名字需要加载,字节码加载完成全部放到了字节数组中,字节组通过自己定义的函数 loadClassBytes(name) 来获取。一旦获取到字节数组,底下的 classloader 里面提供而方法,classloader 实际上是一个抽象类,而他是一个具体类实践的时候需要将方法覆盖到提供的 defineclass类的实现,按类的方法的实现,这个方法要传递 name 就是刚才传递给findclass的名字,即需要加载一个类。ClassBytes 就是获取的字节数组,后面的0表示字节数组里所有的内容全部要去加载,告知起始位和终止的位次、加载的数据量。0表示从第一个字节开始加载,length 表示要将所有的字节全部加载进去。这样 classloader 的 defineclass 会去帮助加载,这样就加载好了。加载字节的方法,首先是名字会原封不动的传递,接下来看是怎样做的,比如名字是com.sum.....一直持续下去,即所谓的包名,加上myclass,假设加密的类是以 Caesar 结尾的,要加载com.sum....myclass.Caesar名字,包名对应的是目录名,应该是com/sum/..../myclass.Caesar一直划到最后,就是将“.”替换成“/”,再加上 caesar,就得到了文件路径,就是将名字替换成了文件的命名,对文件创建一个文件输入流,得到了输入流in=new Fileinputstream,接下来从输入流里面不远的读,是字节就一个个的读,读进来之后判断是否读到了末尾,读到了末尾就是-1,在这个过程中就是不断读里面的内容,读出来让 ch-key,得到的值对应的是 int ch,实际上是将 ch-key 产生的是 byte 字节。因为里面是一个个的数字,所以需要将字节码一个个的数字拿到里面读,读出来之后整个是 byte,放到 buffer 里面,整个循环完之后,buffer里面就是所有读进来的字节,将它转成字节数组,buffer 是字节数组的输出流,将它转成一个数组就结束。所以就是将一个字节码的所有文件读出来减去密钥。

 image.png

有一个解密的类就需要有一个加密的类,加密的类需要判断第一个是需要对哪个类加密,第二个是加密完存到哪里,第三是密钥是什么。所以需要有三个参数,分别对应的是对谁加密,加密之后输出到哪里然后是 key。需要做的事情很简单,就是对着要加密的文件,每一次要读进来的字节加上 key,然后写出到密钥,写出文件就可以。这就可以知道leader 加载器想要自己扩展并不难,通过这种方式就会发现,只有自己的类加载器能加载 caesar 这样的文件,然后交给 Java 运行,其他的类加载器是没有办法直接加载这个类的,所以代码就得到了保护。

现在的问题是要是用 caesar 类加密 args.length !=3这个类,加密完成后让用户使用,本身 Caesar 这个类不需要让用户去使用,用户不知道怎样加密。问题是 cryptoclassloader 本身不能是加密之后的样子,应该是 Java 类加载器本身加载的,本身就是 class文件不可以加密,所以如果写了自己的类加载器,对着类加载器的这个类去做反编译得到代码之后自己去读,还是可以知道是怎样加密的。所以此方法能在一定程度上实现字节码的保护,但不能100%的实现保护,因为可以对自定义的类加载器的反编译知道类是怎样写的,能够得到源码,类加载器要把类加载进来,前面讲要自定义一个类加载器如何实现字节码加密,真正的在加载一个类 defineclass,让类加载器去加载真正解密好的字节或者是没有加载过的类文件,拿来之后给到 defineclass 都做了什么事情。

相关文章
|
存储 JSON 安全
手摸手教你定制 Spring Security 表单登录
手摸手教你定制 Spring Security 表单登录
|
安全 Java 编译器
Security1 1(二)|学习笔记
快速学习 Security1 1(二)
Security1 1(二)|学习笔记
|
安全 Java 数据安全/隐私保护
Security1 1(三)|学习笔记
快速学习 Security1 1(三)
103 0
Security1 1(三)|学习笔记
|
存储 算法 NoSQL
Caching(二)|学习笔记
快速学习 Caching(二)
123 0
Caching(二)|学习笔记
|
存储 缓存 JSON
Caching(三)|学习笔记
快速学习 Caching(三)
157 0
Caching(三)|学习笔记
|
存储 缓存 NoSQL
Caching(一)|学习笔记
快速学习 Caching(一)
143 0
Caching(一)|学习笔记
|
消息中间件 缓存 前端开发
Messaging2(二)|学习笔记
快速学习 Messaging2(二)
Messaging2(二)|学习笔记
|
消息中间件 SQL Java
Messaging1(三)|学习笔记
快速学习 Messaging1(三)
125 0
Messaging1(三)|学习笔记
|
消息中间件 存储 负载均衡
Messaging2(三)|学习笔记
快速学习 Messaging2(三)
120 0
Messaging2(三)|学习笔记
|
消息中间件 传感器 算法
Messaging1(一)|学习笔记
快速学习 Messaging1(一)
125 0
Messaging1(一)|学习笔记