Java安全架构与部署全解析
1. 信任管理器工厂类(TrustManagerFactory Class)
javax.net.ssl.TrustManagerFactory
是一个基于提供者的服务引擎类,可作为一种或多种
TrustManager
对象的工厂。Sun Microsystems 的 SunJSSE 提供者实现了一个工厂,可返回基本的 X.509 信任管理器。从 J2SE 1.4.2 开始,除了简单的传统 SunX509 信任管理器外,Sun Microsystems 还提供了基于证书路径的 PKIX 信任管理器(SunPKIX)。
1.1 创建实例
要创建
TrustManagerFactory
实例,需调用静态的
getInstance
方法,并传入算法名称字符串和可选的提供者规范:
public static TrustManagerFactory getInstance(String algorithm);
public static TrustManagerFactory getInstance(String algorithm, String provider);
public static TrustManagerFactory getInstance(String algorithm, Provider provider);
示例算法名称字符串为
"SunX509"
,示例调用如下:
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
上述调用创建了 SunJSSE 提供者的默认信任管理器工厂实例,该实例提供基本的基于 X.509 的认证路径有效性检查。
1.2 初始化工厂
新创建的工厂应通过调用以下
init
方法之一进行初始化:
public void init(KeyStore ks);
public void init(ManagerFactoryParameters spec);
调用哪个
init
方法取决于所使用的
TrustManagerFactory
的具体情况(可咨询提供者供应商)。对于 SunJSSE 提供者的默认 SunX509
TrustManagerFactory
,初始化该工厂仅需要
java.security.KeyStore
信息,因此应调用第一个
init
方法。
TrustManagerFactory
将查询
KeyStore
,以获取在授权检查期间应信任哪些远程证书的信息。
在某些情况下,提供者可能需要除
KeyStore
之外的初始化参数。使用该特定提供者的用户需要传递提供者定义的适当
ManagerFactoryParameters
实现。例如,假设
TrustManagerFactory
提供者需要特定的初始化参数,调用应用程序可能需要实现并创建
MyTrustManagerFactoryParams
实例:
public interface MyTrustManagerFactoryParams extends ManagerFactoryParameters {
public boolean getBValue();
public float getRValue();
public String getSValue();
}
一些信任管理器能够在未显式使用
KeyStore
对象或其他参数初始化的情况下做出信任决策。例如,它们可能通过 LDAP 从本地目录服务访问信任材料,使用远程在线证书状态检查服务器,或从标准本地位置访问默认信任材料。
2. 密钥管理器接口(KeyManager Interface)
KeyManager
的主要职责是选择最终将发送到远程主机的认证凭证。要将本地安全套接字认证到远程安全套接字对等方,需要使用一个或多个
KeyManager
初始化
SSLContext
对象。对于每种认证机制,都需要传递一个
KeyManager
。如果在
SSLContext
初始化时传入
null
,则不支持任何
KeyManager
;如果使用内部默认上下文,则默认上下文有一个单一的密钥管理器。
通常,
SSLContext
支持基于 X.509 公钥证书的认证。一些安全套接字实现也可能支持基于共享密钥、Kerberos 或其他机制的认证。
3. 密钥管理器工厂类(KeyManagerFactory Class)
javax.net.ssl.KeyManagerFactory
类是一个基于提供者的服务引擎类,可作为一种或多种
KeyManager
对象的工厂。SunJSSE 提供者实现了一个工厂,可返回基本的 X.509 密钥管理器。从 J2SE 1.4.2 开始,除了简单的传统 SunX509 密钥管理器外,Sun Microsystems 还提供了基于证书路径的 PKIX 密钥管理器(SunPKIX)。
3.1 创建实例
创建该类实例的方式与
SSLContext
类似,只是在
getInstance
方法中传入算法名称字符串而非协议名称:
public static KeyManagerFactory getInstance(String algorithm);
public static KeyManagerFactory getInstance(String algorithm, String provider);
public static KeyManagerFactory getInstance(String algorithm, Provider provider);
示例算法名称字符串为
"SunX509"
,示例调用如下:
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509", "SunJSSE");
上述调用创建了 SunJSSE 提供者的默认密钥管理器工厂实例,该实例提供基本的基于 X.509 的认证密钥。
3.2 初始化工厂
新创建的工厂应通过调用以下
init
方法之一进行初始化:
public void init(KeyStore ks, char[] password);
public void init(ManagerFactoryParameters spec);
应调用适合所使用的
KeyManagerFactory
的
init
方法。对于 SunJSSE 提供者的默认 SunX509
KeyManagerFactory
,初始化该工厂仅需要
KeyStore
和密码,因此应调用第一个
init
方法。
KeyManagerFactory
将查询
KeyStore
,以获取用于向远程套接字对等方认证的私钥和匹配的公钥证书信息。密码参数指定用于从
KeyStore
访问密钥的方法所使用的密码,
KeyStore
中的所有密钥必须由相同的密码保护。
在某些情况下,提供者可能需要除
KeyStore
和密码之外的初始化参数。使用该特定提供者的用户需要传递提供者定义的适当
ManagerFactoryParameters
实现。一些工厂能够在未使用
KeyStore
对象或其他参数初始化的情况下提供对认证材料的访问。例如,它们可能作为登录机制(如基于 JAAS 的机制)的一部分访问密钥材料。
4. 远程方法调用(Remote Method Invocation,RMI)
4.1 RMI 基本原理
远程方法调用(RMI)使一个 Java 虚拟机中的对象能够无缝调用远程 Java 虚拟机中的对象的方法。为实现远程调用,RMI 存根(stub)使用对象序列化对方法参数进行编组,并将编组后的调用信息通过网络发送到服务器。在服务器端,RMI 系统接收调用并将其连接到骨架(skeleton),骨架负责解组参数并调用服务器端的方法实现。当服务器端的实现完成后,骨架将结果编组并向客户端存根发送回复,存根解组回复并返回值或抛出异常。
4.2 RMI 安全基础
这种分布式编程模型存在明显的安全隐患。为降低风险,所有使用 RMI 的程序都必须安装安全管理器,否则 RMI 不会从本地类路径之外下载作为远程方法调用的参数、返回值或异常接收的对象的类。此限制确保下载的类执行的操作经过一系列安全检查。
RMI 对客户端施加的访问控制限制与 applet 类似。RMI 要求客户端具有与等效
java.net.Socket
网络通信相同的
SocketPermissions
,即“connect”以对给定主机和端口上导出的远程对象进行远程调用。在服务器端,RMI 要求具有与等效
java.net.ServerSocket
网络通信相同的
SocketPermissions
,即“listen”以在给定端口上导出远程对象,“accept”以接收从给定主机和端口发起的远程调用。此外,当 RMI 调用分派到服务器上导出的对象时,将恢复该远程对象导出时生效的
AccessControlContext
。
4.3 RMI 激活
rmid
是随 J2SDK 1.2 版本引入的。现在可以编写程序来注册有关应“按需”创建和执行的远程对象实现的信息。RMI 守护进程提供一个 Java 虚拟机,可从该虚拟机启动其他 JVM 实例。
当
rmid
为激活组启动 JVM 时,它使用组的可选
ActivationGroupDesc.CommandEnvironment
中的信息,其中包括启动激活组要执行的命令以及要添加到命令行的任何命令行选项。为确保安全,Sun 对 RMI 激活守护进程的实现依赖于安全策略,以便
rmid
可以验证每个
ActivationGroupDesc
中的信息是否允许用于启动激活组。
com.sun.rmi.rmid.ExecPermission
用于授予
rmid
执行命令以启动激活组的权限,
com.sun.rmi.rmid.ExecOptionPermission
用于允许
rmid
在启动激活组时使用组描述符中指定的属性覆盖或
CommandEnvironment
中的命令行选项。
4.4 保护 RMI 通信
RMI 完全使用序列化来实现远程方法调用。对象序列化存在安全风险,当使用 RMI 时,对象在两个 Java 虚拟机之间来回传输,传输过程中序列化数据可能会暴露给攻击者,从而导致数据的完整性或机密性受损。
在 J2SE 中,RMI 为基于 RMI 的通信添加了对自定义套接字工厂的支持。因此,RMI 应用程序可以导出远程对象以使用创建 SSL 套接字的 RMI 套接字工厂。以下是一个简单的
java.rmi.server.RMIClientSocketFactory
实现示例:
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;
public class RMISSLClientSocketFactory implements RMIClientSocketFactory, java.io.Serializable {
public Socket createSocket(String host, int port) throws IOException {
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
return socket;
}
}
实现等效的服务器套接字也并不复杂,以下是
java.rmi.server.RMIServerSocketFactory
的实现示例:
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagerFactory;
import java.security.KeyStore;
public class RMISSLServerSocketFactory implements RMIServerSocketFactory, java.io.Serializable {
public SSLServerSocket createServerSocket(int port) throws IOException {
SSLServerSocketFactory ssf = null;
try {
SSLContext ctx;
KeyManagerFactory kmf;
KeyStore ks;
char[] passphrase = "open sesame".toCharArray();
ctx = SSLContext.getInstance("TLS");
kmf = KeyManagerFactory.getInstance("SunX509");
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("testkeys"), passphrase);
kmf.init(ks, passphrase);
ctx.init(kmf.getKeyManagers(), null, null);
ssf = ctx.getServerSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return (SSLServerSocket) ssf.createServerSocket(port);
}
}
要将其结合使用,在调用
java.rmi.server.UnicastRemoteObject
构造函数时,导出的远程对象必须指定两个 RMI 套接字实现类:
super(0, new RMISSLClientSocketFactory(), new RMISSLServerSocketFactory());
5. Java 安全架构部署
5.1 安装最新的 Java 2 平台软件
Java 2 平台标准版(J2SE)适用于 Windows、Solaris 和 Linux 的软件可从 Sun Microsystems 的 Java 产品和 API 网站(http://java.sun.com/products/)获取。通常,新版本的操作系统或 Internet 浏览器(如安装的升级版本或新计算机系统附带的软件)已经支持最新的 Java 运行时环境(JRE)。若要自行升级 JVM,可以下载并安装以下任意一项:
1. JRE,用于运行 Java 应用程序和 applet。
2. 用于开发的 SDK,其中也包含 JRE。
3. Java 插件,用于升级 Microsoft Internet Explorer(IE)、Netscape Navigator 和 Mozilla 中的 JVM。
5.2 安装目录
<java.home>
<java.home>
指的是
java.home
系统属性的值,该属性指定 JRE 的安装目录,即 JRE 的顶级目录或 Java 2 SDK(J2SDK)软件中的
jre
目录。例如:
| 操作系统 | 安装情况 |
<java.home>
指向目录 |
| — | — | — |
| Solaris | 安装 J2SDK 1.4 于
/home/user1/j2sdk1.4.0
|
/home/user1/j2sdk1.4.0/jre
|
| Win32 | 安装 J2SDK 1.4 于
C:\j2sdk1.4.0
|
C:\j2sdk1.4.0\jre
|
| Solaris | 安装 JRE 1.4 于
/home/user1/j2re1.4.0
|
/home/user1/j2re1.4.0
|
| Win32 | 安装 JRE 1.4 于
C:\j2re1.4.0
|
C:\j2re1.4.0
|
5.3 设置系统和安全属性
5.3.1 设置系统属性
可以静态或动态设置系统属性。静态设置系统属性可使用
java
命令的
-D
选项。例如,运行名为
MyApp
的应用程序并将
user.home
系统属性设置为指定用户主目录
/home/marys
,可使用以下命令:
java -Duser.home=/home/marys MyApp
动态设置系统属性可在代码中调用
java.lang.System.setProperty
方法,例如:
System.setProperty("user.home", "/home/marys");
一些系统属性有默认值,例如
java.home
属性默认指向 Java 2 运行时环境的安装目录,
user.home
系统属性的默认值取决于操作系统:
- 在 Solaris 系统上,
user.home
系统属性值默认为用户的主目录。
- 在 Windows 系统上,根据不同版本和用户设置,默认值有所不同:
- 多用户 Windows 95/98/ME:
C:\Documents and Settings\uName
- Windows 2000 和 XP:
C:\Winnt\Profiles\uName
- 多用户 Windows NT:
C:\Windows\Profiles\uName
- 单用户 Windows 95/98/ME:
C:\Windows
以下程序可用于确定
user.home
的值:
public class getUserHome {
public static void main(String[] args) {
System.out.println("user.home is " + System.getProperty("user.home"));
}
}
5.3.2 设置安全属性
Java 安全的某些方面可以通过设置安全属性进行定制。与系统属性一样,安全属性可以静态或动态设置。静态设置安全属性可修改安全属性文件,默认情况下,随 Java 2 版本发布的安全属性文件安装在以下位置:
- Solaris、Linux:
<java.home>/lib/security/java.security
- Win32:
<java.home>\lib\security\java.security
可以通过设置
java.security.properties
系统属性指定要使用的安全属性文件的位置。在默认安全属性文件中,各种安全属性的默认值已初始设置。要更改安全属性值,可在安全属性文件中添加以下形式的行:
propertyName=propertyValue
6. 部署定制与工具使用
6.1 定制部署
可以通过覆盖或追加安全属性以及在运行应用程序时指定特定于应用程序的策略来定制部署。例如,在运行应用程序时,可以通过命令行参数或配置文件指定特定的安全策略,以控制代码对敏感资源的访问。
6.2 安装提供者包
可以安装提供 Java 2 安全 API 或其扩展的一部分加密或其他安全服务具体实现的提供者包。安装后,应用程序可以使用这些提供者提供的功能,增强安全性能。
6.3 安全策略指定
可以指定安全策略,以指示来自特定源的代码允许进行哪些安全敏感资源访问。可以在一个或多个文件中指定策略,其格式应能被默认的
Policy
实现处理。如果需要,也可以配置不同的
Policy
实现。
6.4 登录配置文件
可以创建和使用登录配置文件,供 Java 认证和授权服务(JAAS)使用。JAAS 可用于用户认证和授权,通过配置登录配置文件,可以定义不同的认证和授权机制。
6.5 安全命令行工具
-
keytool
:用于管理私钥及其关联的公钥认证证书的密钥库(数据库),还可管理来自可信实体的证书。例如,可以使用
keytool创建、导入和导出证书,以及管理密钥库中的密钥。 - jarsigner :用于为 Java 归档(JAR)文件生成数字签名,并验证已签名 JAR 文件签名的真实性。通过对 JAR 文件进行签名,可以确保文件的完整性和来源的可靠性。
6.6 X.500 可分辨名称
X.500 可分辨名称用于唯一标识实体,在 Java 安全中常用于证书和身份验证。例如,在证书中,X.500 可分辨名称可以包含组织、组织单位、国家等信息,用于准确标识证书的所有者。
7. 总结
Java 安全架构涵盖了多个重要方面,包括信任管理器和密钥管理器的使用、远程方法调用的安全机制以及安全架构的部署和定制。通过合理使用这些安全特性和工具,可以有效保护 Java 应用程序的安全,确保数据的完整性和机密性。在实际应用中,需要根据具体需求和场景,选择合适的安全策略和配置,以满足不同的安全要求。同时,不断关注 Java 安全领域的最新发展,及时更新和改进安全措施,以应对日益复杂的安全挑战。
8. 安全策略配置示例
为了更清晰地说明如何指定安全策略,下面给出一个具体的示例。假设我们有一个 Java 应用程序,需要对不同来源的代码授予不同的权限。我们可以创建一个安全策略文件,例如
myapp.policy
,内容如下:
// 授予本地代码所有权限
grant codeBase "file:/path/to/local/code/" {
permission java.security.AllPermission;
};
// 授予来自特定远程服务器的代码部分权限
grant codeBase "http://example.com/remote/code/" {
permission java.io.FilePermission "/path/to/allowed/files/*", "read,write";
permission java.net.SocketPermission "example.com:80", "connect";
};
// 拒绝其他所有代码的权限
grant {
permission java.security.NoPermission;
};
在这个示例中,我们定义了三个不同的代码库(
codeBase
)的权限:
1. 本地代码(
file:/path/to/local/code/
)被授予所有权限,这意味着它可以执行任何操作。
2. 来自
http://example.com/remote/code/
的代码被授予读取和写入特定文件路径的权限,以及连接到
example.com
的 80 端口的权限。
3. 其他所有代码被拒绝所有权限,这是一个安全的默认设置,防止未授权的代码执行敏感操作。
要使用这个安全策略文件,我们可以在运行 Java 应用程序时指定它:
java -Djava.security.policy=myapp.policy MyApp
9. 登录配置文件示例
Java 认证和授权服务(JAAS)使用登录配置文件来定义认证和授权机制。下面是一个简单的登录配置文件示例,假设我们使用基于文本文件的认证:
MyLoginContext {
com.sun.security.auth.module.TextFileLoginModule required
debug=true
userFile="/path/to/users.txt"
groupFile="/path/to/groups.txt";
};
在这个示例中:
-
MyLoginContext
是登录上下文的名称,应用程序可以使用这个名称来引用这个配置。
-
com.sun.security.auth.module.TextFileLoginModule
是一个内置的登录模块,用于基于文本文件进行认证。
-
required
表示这个登录模块是必需的,如果认证失败,整个登录过程将失败。
-
debug=true
启用调试模式,方便排查问题。
-
userFile
和
groupFile
分别指定了包含用户信息和组信息的文本文件的路径。
要在 Java 代码中使用这个登录配置文件,我们可以这样做:
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
public class MyApp {
public static void main(String[] args) {
try {
// 创建登录上下文
LoginContext lc = new LoginContext("MyLoginContext");
// 进行登录
lc.login();
// 登录成功,执行后续操作
System.out.println("Login successful!");
// 注销
lc.logout();
} catch (LoginException le) {
System.err.println("Login failed: " + le.getMessage());
}
}
}
10. 安全工具使用流程
10.1 keytool 使用流程
以下是使用
keytool
管理密钥库和证书的基本流程:
graph LR
A[创建密钥库] --> B[生成密钥对]
B --> C[导出证书]
C --> D[导入证书到其他密钥库]
D --> E[查看密钥库内容]
E --> F[删除密钥或证书]
- 创建密钥库 :
keytool -genkeypair -alias mykey -keyalg RSA -keystore mykeystore.jks -keysize 2048
这个命令创建了一个名为
mykeystore.jks
的密钥库,并生成了一个名为
mykey
的 RSA 密钥对。
-
生成密钥对 :如果需要生成新的密钥对,可以使用类似上面的命令,只需更改别名和其他参数。
-
导出证书 :
keytool -exportcert -alias mykey -keystore mykeystore.jks -file mycert.crt
这个命令将
mykey
对应的证书导出到
mycert.crt
文件中。
- 导入证书到其他密钥库 :
keytool -importcert -alias mycert -file mycert.crt -keystore otherkeystore.jks
这个命令将
mycert.crt
证书导入到
otherkeystore.jks
密钥库中。
- 查看密钥库内容 :
keytool -list -keystore mykeystore.jks
这个命令列出
mykeystore.jks
密钥库中的所有条目。
- 删除密钥或证书 :
keytool -delete -alias mykey -keystore mykeystore.jks
这个命令删除
mykeystore.jks
密钥库中名为
mykey
的条目。
10.2 jarsigner 使用流程
以下是使用
jarsigner
对 JAR 文件进行签名和验证的基本流程:
graph LR
A[准备 JAR 文件和密钥库] --> B[对 JAR 文件进行签名]
B --> C[验证 JAR 文件签名]
-
准备 JAR 文件和密钥库 :确保你有一个要签名的 JAR 文件和包含私钥的密钥库。
-
对 JAR 文件进行签名 :
jarsigner -keystore mykeystore.jks myapp.jar mykey
这个命令使用
mykeystore.jks
密钥库中的
mykey
私钥对
myapp.jar
文件进行签名。
- 验证 JAR 文件签名 :
jarsigner -verify myapp.jar
这个命令验证
myapp.jar
文件的签名是否有效。
11. 安全架构的持续优化
11.1 监控和审计
定期监控 Java 应用程序的安全日志,检查是否有异常的安全事件发生。可以使用日志管理工具(如 Logstash、Elasticsearch 和 Kibana)来收集、存储和分析安全日志。同时,进行定期的安全审计,检查安全策略的配置是否符合最佳实践,是否存在潜在的安全漏洞。
11.2 漏洞修复和更新
及时关注 Java 平台和相关安全库的安全更新,确保应用程序使用的是最新版本。当发现安全漏洞时,尽快应用修复补丁,以防止攻击者利用漏洞进行攻击。
11.3 安全培训和意识提升
对开发人员和运维人员进行安全培训,提高他们的安全意识和技能。让他们了解 Java 安全架构的基本知识和最佳实践,以及如何编写安全的代码和配置安全策略。
12. 不同场景下的安全策略选择
12.1 企业内部应用
对于企业内部的 Java 应用,安全策略可以相对宽松一些,但仍然需要确保数据的保密性和完整性。可以使用基于角色的访问控制(RBAC)来管理用户的权限,根据用户的角色和职责授予不同的权限。同时,对内部网络进行分段,限制不同部门之间的访问,防止内部攻击。
12.2 面向公众的 Web 应用
面向公众的 Web 应用面临更多的安全威胁,需要更严格的安全策略。可以使用 HTTPS 协议来加密数据传输,防止中间人攻击。同时,对用户输入进行严格的验证和过滤,防止 SQL 注入、跨站脚本攻击(XSS)等常见的 Web 安全漏洞。
12.3 移动应用后端
移动应用后端需要考虑移动设备的特殊性,如网络环境不稳定、设备资源有限等。可以使用 OAuth 2.0 等协议进行用户认证和授权,确保用户的身份安全。同时,对移动应用和后端服务器之间的数据传输进行加密,防止数据泄露。
13. 总结与展望
Java 安全架构为 Java 应用程序提供了强大的安全保障,通过合理配置信任管理器、密钥管理器、安全策略和使用安全工具,可以有效保护应用程序的安全。在实际应用中,需要根据不同的场景和需求,选择合适的安全策略和配置,不断优化和改进安全措施。
随着信息技术的不断发展,安全威胁也在不断变化。未来,Java 安全架构需要不断适应新的安全挑战,如物联网、大数据、人工智能等领域带来的安全问题。同时,需要加强与其他安全技术的融合,如区块链、零信任架构等,为 Java 应用程序提供更全面、更强大的安全保护。
超级会员免费看

被折叠的 条评论
为什么被折叠?



