47、Java安全与应用部署全解析

Java安全与应用部署全解析

1. Java安全基础

1.1 消息认证码计算

消息认证码(MAC)用于验证数据的完整性和真实性。以下是一个创建 Mac 类实例并计算数据消息认证码的示例代码:

import java.security.*;
import javax.crypto.*;
import java.io.*;

public class MacExample {
    public static void main(String args[]) {
        try {
            String inputString = "Test input string";
            KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
            SecretKey secretKey = keyGen.generateKey();
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            // the Mac class needs data in byte format
            byte[] byteData = inputString.getBytes("UTF8");
            // Compute the MAC for the data all in one operation
            byte[] macBytes = mac.doFinal(byteData);
            String macAsString = new sun.misc.BASE64Encoder().encode(macBytes);
            System.out.println(
                "The computed message authentication code is: " + macAsString);
        } catch (InvalidKeyException e) {
        } catch (NoSuchAlgorithmException e) {
        } catch (UnsupportedEncodingException e) {
        }
    }
}

计算消息认证码与使用 MessageDigest 引擎类计算消息摘要非常相似,主要区别在于需要一个密钥。在上述示例中,密钥通过 KeyGenerator 生成,通常这个密钥会被保存或传输,用于验证 MAC 是否与数据匹配。

1.2 JAAS简介

JAAS(Java Authentication and Authorization Service)用于提供身份验证和授权服务,它在 JDK 1.4 版本中成为 J2SDK 的一部分。身份验证是验证应用程序用户(包括 applet、servlet 等任何类型的 Java 程序)的过程,而授权是为经过身份验证的用户授予执行操作(如修改特定访问控制文件)权限的过程。身份验证和授权共同为程序提供访问控制。

1.2.1 用户标识

为了实现访问控制,需要一种存储用户身份的方法,这通过 Subject 来完成。 Subject 是一组信息,用于标识所有请求的来源,例如登录到系统的特定用户。 Subject 具有关联的主体(principals)和公共、私有凭据(credentials)。

Subject 提供了两个构造函数:

public Subject();
public Subject(boolean readOnly, Set principals, Set pubCredentials, Set privCredentials);

第一个构造函数创建一个非只读的 Subject ,其主体、公共和私有凭据集为空(非 null);第二个构造函数展示了 Subject 所包含的信息。

Subject 还提供了以下方法来更改和检索只读状态:

public void setReadOnly();
public boolean isReadOnly();

以及以下方法来检索主体、公共或私有凭据集的句柄:

public Set getPrincipals();
public Set getPrincipals(Class c);
public Set getPublicCredentials();
public Set getPublicCredentials(Class c);
public Set getPrivateCredentials();
public Set getPrivateCredentials(Class c);

每个访问方法的版本都有一个 Class 参数,允许仅检索特定类型的主体/凭据,但这些方法返回的是一个新集,与 Subject 的内部集不对应。

Subject 可以与 AccessControlContext 关联,通过以下静态方法访问:

public static Subject getSubject(final AccessControlContext acc);
1.2.2 执行带安全检查的代码

Subject 类提供了 doAs doAsPrivileged 方法来执行包含安全限制的代码。要使用这些方法,需要实现 java.security.PrivilegedAction 接口,该接口只定义了一个方法:

public Object run();

doAs 方法的定义如下:

public static Object doAs(final Subject subject, final java.security.PrivilegedAction action);
public static Object doAs(final Subject subject, final java.security.PrivilegedExceptionAction action) throws java.security.PrivilegedActionException;

doAsPrivileged 方法的定义如下:

public static Object doAsPrivileged(final Subject subject, final java.security.PrivilegedAction action, final java.security.AccessControlContext acc);
public static Object doAsPrivileged(final Subject subject, final java.security.PrivilegedExceptionAction action, final java.security.AccessControlContext acc) throws java.security.PrivilegedActionException;

这些方法首先将 Subject 与当前线程的 AccessControlContext 关联,然后执行操作。 doAsPrivileged 方法允许指定要使用的 AccessControlContext ,而不是使用当前线程的。

1.2.3 主体(Principals)

主体可以是任何继承自 java.security.Principal java.io.Serializable 的类类型。 Principal 接口定义了以下方法:

boolean equals(Object another);
String toString();
int hashCode();
String getName();

当一个实体进行身份验证时,必须提供凭据(安全系统用于验证该实体的信息),例如用户登录系统时需要提供用户名和密码。

1.2.4 凭据(Credentials)

凭据可以是任何类型,JAAS 提供了 Refreshable Destroyable 两个接口,为凭据类赋予了一些有用的行为。

javax.security.auth.Refreshable 接口适用于需要刷新状态的凭据,定义了以下方法:

boolean isCurrent();
void refresh() throws RefreshFailedException;

javax.security.auth.Destroyable 接口为凭据提供了销毁内容的语义,定义了以下方法:

boolean isDestroyed();
void destroy() throws DestroyFailedException;

调用 destroy 方法后,需要凭据内容有效的方法应抛出 IllegalStateException

1.2.5 身份验证主体

主体的基本身份验证方式是通过 LoginContext 对象,其身份验证步骤如下:
1. 实例化 LoginContext 对象。
2. LoginContext 咨询 Configuration 以加载当前应用程序的所有 LoginModule
3. 调用 LoginContext login 方法。
4. 每个 LoginModule 尝试对主体进行身份验证,并将主体/凭据与成功验证的用户关联。
5. 将身份验证的成功或失败信息传达回应用程序。

1.2.6 配置文件

配置文件包含每个应用程序的多个身份验证配置,每个配置有一个名称(通常是应用程序名称)和用于身份验证的登录模块列表。配置可以有一组名为 other 的登录模块,用于在没有其他名称匹配时指定身份验证方案。每个登录模块集遵循以下语法:

NAME {
    LoginModuleClass  FLAG  ModuleOptions;
    LoginModuleClass  FLAG  ModuleOptions;
}

LoginModuleClass LoginModule 的完全限定名, FLAG 可以是以下值之一:
| Flag Name | Description |
| — | — |
| Required | 该 LoginModule 必须成功,但如果失败,后续指定的 LoginModule 仍会执行。 |
| Requisite | 该 LoginModule 必须成功,如果失败,控制权返回给应用程序,不再执行其他 LoginModule 。 |
| Sufficient | 该 LoginModule 不必须成功,如果成功,控制权立即返回给应用程序;即使失败,也会继续执行列表中的其他 LoginModule 。 |
| Optional | 该 LoginModule 不必须成功,无论成功或失败,控制权都会继续传递到列表中的下一个。 |
ModuleOptions 是一个以空格分隔的登录模块特定的 name=value 对列表。

1.2.7 LoginContext

LoginContext 类提供了一种简洁的方式来对主体进行身份验证,同时将身份验证细节留给 LoginModule 。它提供了以下构造函数:

public LoginContext(String name) throws LoginException;
public LoginContext(String name, Subject subject) throws LoginException;
public LoginContext(String name, CallbackHandler callbackHandler) throws LoginException;
public LoginContext(String name, Subject subject, CallbackHandler callbackHandler) throws LoginException;

name 参数对应于应用程序使用的配置中的条目。如果 LoginModule 需要与用户通信,可以通过 CallbackHandler 实现, CallbackHandler 接口定义了一个方法:

void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException;

LoginContext 还提供了 login logout 方法:

public void login() throws LoginException;
public void logout() throws LoginException;

以下是一个应用程序登录、获取经过身份验证的主体并注销的示例代码:

LoginContext loginContext = new LoginContext("BasicConsoleLogin");
try {
    loginContext.login(); // utilizes callbacks
    Subject subject = loginContext.getSubject();
    // ... execute specific application code here ...
    loginContext.logout();
} catch(LoginException le) {
    // authentication failed
}
1.2.8 授权

身份验证提供了一种黑白分明的安全方法,用户要么经过验证,要么未经过验证。JAAS 提供授权,用于为实体授予不同程度的访问权限。每个应用程序可以使用一个策略文件,其中包含各种目标的权限列表。

策略文件包含多个 grant 部分,用于为代码或主体授予权限,基本格式如下:

grant signedBy "signer_names",
    codeBase "URL",
    principal principal_class_name "principal_name",
    principal principal_class_name "principal_name",
    ... {
    permission permission_class_name "target_name", "action", signedBy "signer_names";
    permission permission_class_name "target_name", "action", signedBy "signer_names";
    ...
};

signedBy codeBase 最多只能指定一次,而 principal 元素可以指定多次,这些都是可选元素。如果不指定任何元素,指定的权限将应用于所有执行的代码,无论其来源如何。

例如,Java 自带的 jre/lib/security 目录下的 java.policy 文件有一个策略,为 Java 扩展打开了广泛的权限:

grant codeBase "file:${{java.ext.dirs}}/*" {
    permission java.security.AllPermission;
};

2. Java应用部署

2.1 Java类路径问题

Java 类路径是一个系统环境变量,用于指示 Java 虚拟机(VM)查找一组类和/或 JAR 文件,它决定了 VM 知道程序使用的代码所在位置。

类路径可能会带来一些问题:
- 难以定位类 :有时需要一个类,但不知道哪个 JAR 文件包含该类,可能会添加大量 JAR 文件到类路径中,却不确定哪些是不必要的。
- 版本冲突 :类路径中存在同一类的多个版本,可能会导致难以追踪的错误。
- 长度限制 :操作系统对环境变量的长度有限制,过长的类路径在开发和部署过程中都可能带来麻烦。

2.2 管理长类路径的建议

  • 使用相对路径 :了解应用程序的执行位置,使用相对路径代替绝对路径。
  • 合并 JAR 文件 :尝试将应用程序及其库合并到尽可能少的 JAR 文件中。
  • 使用扩展目录 :将常用的实用 JAR 文件(如第三方 JAR 文件)放置在安装的 JRE 的扩展目录(默认是 lib/ext )中,这样这些 JAR 文件就不需要出现在类路径中,但要确保 JAR 文件放置在正确的 JRE 中。

2.3 类路径管理工具

2.3.1 ClassPathVerifier

ClassPathVerifier 是一个简单的实用工具,用于验证文件中存储的类列表是否存在于类路径中。代码如下:

import java.io.*;

public class ClassPathVerifier {
    public static void main(String args[]) {
        try {
            BufferedReader br = new BufferedReader(
                new InputStreamReader(
                    new FileInputStream(args[0])));
            String clsName="";
            while( (clsName = br.readLine()) != null) {
                try {
                    Class.forName(clsName);
                    System.out.print(".");
                } catch(Exception e) {
                    System.out.println("\nNOT FOUND: " + clsName);
                }
            }
            br.close();
        } catch(IOException ioe) {
            System.out.println("IOException: " + ioe);
            ioe.printStackTrace();
        }
    }
}

该类使用 Class.forName 方法,如果没有抛出异常,则表示类已找到。对于每个成功加载的类,会打印一个句点。

2.3.2 ClassSearch

ClassSearch 用于查找包含特定类的 JAR 文件,不需要指定类的完全限定名,任何类名和包的部分都可以。代码如下:

import java.io.*;
import java.util.zip.*;
import java.util.StringTokenizer;

public class ClassSearch {
    private String m_baseDirectory;
    private String m_classToFind;
    private int m_resultsCount=0;

    public void searchJarFile(String filePath) {
        try {
            FileInputStream fis = new FileInputStream(filePath);
            BufferedInputStream bis = new BufferedInputStream(fis);
            ZipInputStream zis = new ZipInputStream(bis);
            ZipEntry ze = null;
            while((ze=zis.getNextEntry()) != null) {
                if(ze.isDirectory()) {
                    continue;
                }
                if(ze.getName().indexOf(m_classToFind) != -1) {
                    System.out.println("  " + ze.getName() + 
                        "\n    (inside " + filePath + ")");
                    m_resultsCount++;
                }
            }
        } catch(Exception e) {
            System.out.println("Exception: " + e);
            e.printStackTrace();
        }
    }

    public void findHelper(File dir, int level) {
        int i;
        File[] subFiles;
        subFiles = dir.listFiles();
        if(subFiles == null) {
            return;
        }
        for(i=0; i<subFiles.length; i++) {
            if(subFiles[i].isFile()) {
                if(subFiles[i].getName().toLowerCase().indexOf(".jar") != -1) {
                    // found a jar file, process it
                    searchJarFile(subFiles[i].getAbsolutePath());
                }
            } else if(subFiles[i].isDirectory()) {
                // directory, so recur
                findHelper(subFiles[i], level+1);
            }
        }
    }

    public void searchClassPath(String classToFind) {
        String classPath = System.getProperty("java.class.path");
        System.out.println("Searching classpath: " + classPath);
        StringTokenizer st = new StringTokenizer(classPath, ";");
        m_classToFind = classToFind;
        while(st.hasMoreTokens()) {
            String jarFileName = st.nextToken();
            if(jarFileName != null && 
                jarFileName.toLowerCase().indexOf(".jar") != -1) {
                searchJarFile(jarFileName);
            }
        }
    }

    public void findClass(String baseDir, String classToFind) {
        System.out.println("SEARCHING IN: " + baseDir);
        m_baseDirectory = baseDir;
        m_classToFind = classToFind;
        m_classToFind = m_classToFind.replaceAll("\\.", "/");
        File start = new File(m_baseDirectory);
        System.out.println("SEARCHING FOR: " + m_classToFind);
        System.out.println("\nSEARCH RESULTS:");
        findHelper(start, 1);
        if(m_resultsCount == 0) {
            System.out.println("No results.");
        }
    }

    public static void main(String args[]) {
        if(args.length < 1 || args.length > 2) {
            System.out.println("Incorrect program usage");
            System.out.println("  java ClassSearch <base directory> <class to find>\n");
            System.out.println("    searches all jar files beneath base directory for class\n");
            System.out.println("");
            System.out.println("  java ClassSearch <class to find>\n");
            System.out.println("    searches all jar files in classpath for class\n");
            System.exit(1);
        }
        ClassSearch cs = new ClassSearch();
        if(args.length == 1) {
            cs.searchClassPath(args[0]);
        } else if(args.length == 2) {
            cs.findClass(args[0], args[1]);
        }
    }
}

该类使用 Java 的 zip 库和 File 类的目录搜索功能,根据命令行指定的类/包进行搜索。例如,以下命令用于在指定目录下搜索 RSAPrivateKey 类:

c:\>java ClassSearch "c:\program files\java\jdk1.6.0" RSAPrivateKey

输出结果可能如下:

SEARCHING IN: c:\program files\java\jdk1.6.0
SEARCHING FOR: RSAPrivateKey
SEARCH RESULTS:
  com/sun/deploy/security/MozillaJSSRSAPrivateKey.class
    (inside c:\program files\java\jdk1.6.0\jre\lib\deploy.jar)
  com/sun/deploy/security/MSCryptoRSAPrivateKey.class
    (inside c:\program files\java\jdk1.6.0\jre\lib\deploy.jar)
  sun/security/mscapi/RSAPrivateKey.class
    (inside c:\program files\java\jdk1.6.0\jre\lib\ext\sunmscapi.jar)
  sun/security/pkcs11/P11Key$P11RSAPrivateKey.class
    (inside c:\program files\java\jdk1.6.0\jre\lib\ext\sunpkcs11.jar)
  java/security/interfaces/RSAPrivateKey.class

通过以上介绍,我们了解了 Java 安全和应用部署的相关知识,包括消息认证码计算、JAAS 身份验证和授权、类路径管理等内容,这些知识对于开发和部署安全可靠的 Java 应用程序非常重要。

2.4 Java Web Start

Java Web Start 是一种允许用户通过网络直接启动 Java 应用程序的技术。它提供了一种方便的方式来部署和更新 Java 应用,用户无需手动下载和安装应用程序。

2.4.1 工作原理

Java Web Start 的工作流程如下:

graph LR
    A[用户访问启动文件] --> B[Java Web Start 下载启动文件]
    B --> C{检查缓存}
    C -->|文件存在且未过期| D[从缓存加载应用]
    C -->|文件不存在或已过期| E[下载应用资源]
    E --> F[验证资源完整性]
    F --> G[启动应用程序]
  1. 用户通过点击网页上的链接或运行 .jnlp 文件来访问 Java 应用程序。
  2. Java Web Start 下载 .jnlp 文件,该文件包含了应用程序的配置信息,如应用程序的名称、版本、所需的 JAR 文件等。
  3. Java Web Start 检查本地缓存中是否已经存在所需的 JAR 文件,如果存在且未过期,则直接从缓存中加载;否则,下载所需的 JAR 文件。
  4. 验证下载的资源的完整性,确保文件没有被篡改。
  5. 启动 Java 应用程序。
2.4.2 示例 .jnlp 文件
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="http://example.com/app/" href="app.jnlp">
    <information>
        <title>My Java Application</title>
        <vendor>My Company</vendor>
        <description>An example Java application</description>
    </information>
    <resources>
        <j2se version="1.8+" />
        <jar href="app.jar" main="true" />
    </resources>
    <application-desc main-class="com.example.Main" />
</jnlp>

2.5 JAR 包装与签名

2.5.1 JAR 包装

JAR(Java Archive)文件是一种用于打包 Java 类文件、资源文件和元数据的文件格式。它可以将多个文件组合成一个单一的文件,方便应用程序的分发和部署。

使用 jar 命令可以创建和管理 JAR 文件,以下是一些常见的操作:
- 创建 JAR 文件:

jar cvf myapp.jar com/example/*.class
  • 查看 JAR 文件内容:
jar tf myapp.jar
  • 提取 JAR 文件内容:
jar xvf myapp.jar
2.5.2 JAR 签名

JAR 签名用于验证 JAR 文件的完整性和来源。签名后的 JAR 文件可以确保文件在传输过程中没有被篡改,并且可以确认文件的发布者。

签名 JAR 文件的步骤如下:
1. 创建密钥库:

keytool -genkeypair -alias mykey -keyalg RSA -keystore mykeystore.jks
  1. 签名 JAR 文件:
jarsigner -keystore mykeystore.jks myapp.jar mykey
  1. 验证 JAR 文件签名:
jarsigner -verify myapp.jar

2.6 构建 WAR 文件

WAR(Web Application Archive)文件是一种专门用于打包 Web 应用程序的 JAR 文件。它包含了 Web 应用程序的所有资源,如 JSP 文件、Servlet 类、HTML 文件、CSS 文件等。

2.6.1 WAR 文件结构

一个典型的 WAR 文件结构如下:

myapp.war
├── WEB-INF
│   ├── web.xml
│   ├── classes
│   │   └── com
│   │       └── example
│   │           └── MyServlet.class
│   └── lib
│       └── mylibrary.jar
├── index.jsp
├── css
│   └── style.css
└── images
    └── logo.png
2.6.2 构建 WAR 文件

可以使用 jar 命令或构建工具(如 Maven 或 Gradle)来构建 WAR 文件。以下是使用 jar 命令构建 WAR 文件的示例:

jar cvf myapp.war WEB-INF index.jsp css images

2.7 总结

Java 应用的部署涉及到多个方面,包括类路径管理、Java Web Start、JAR 包装与签名、WAR 文件构建等。合理使用这些技术可以提高应用程序的部署效率和安全性。

  • 类路径管理 :通过使用相对路径、合并 JAR 文件和使用扩展目录等方法,可以有效管理长类路径,避免类路径带来的问题。
  • Java Web Start :提供了一种方便的方式来部署和更新 Java 应用程序,用户可以通过网络直接启动应用。
  • JAR 包装与签名 :JAR 文件可以将多个文件组合成一个单一的文件,方便分发和部署;签名可以确保文件的完整性和来源。
  • WAR 文件构建 :WAR 文件用于打包 Web 应用程序,包含了 Web 应用的所有资源。

在实际开发中,需要根据应用程序的需求和特点,选择合适的部署方式和技术,以确保应用程序的安全、稳定和高效运行。

【无人机】基于改进粒子群算法的无人机路径规划研究[和遗传算法、粒子群算法进行比较](Matlab代码实现)内容概要:本文围绕基于改进粒子群算法的无人机路径规划展开研究,重点探讨了在复杂环境中利用改进粒子群算法(PSO)实现无人机三维路径规划的方法,并将其遗传算法(GA)、标准粒子群算法等传统优化算法进行对比分析。研究内容涵盖路径规划的多目标优化、避障策略、航路点约束以及算法收敛性和寻优能力的评估,所有实验均通过Matlab代码实现,提供了完整的仿真验证流程。文章还提到了多种智能优化算法在无人机路径规划中的应用比较,突出了改进PSO在收敛速度和局寻优方面的优势。; 适合人群:具备一定Matlab编程基础和优化算法知识的研究生、科研人员及从事无人机路径规划、智能优化算法研究的相关技术人员。; 使用场景及目标:①用于无人机在复杂地形或动态环境下的三维路径规划仿真研究;②比较不同智能优化算法(如PSO、GA、蚁群算法、RRT等)在路径规划中的性能差异;③为多目标优化问题提供算法选型和改进思路。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注算法的参数设置、适应度函数设计及路径约束处理方式,同时可参考文中提到的多种算法对比思路,拓展到其他智能优化算法的研究改进中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值