为什么需要对jar文件进行签名?
我无法回答这个问题的一部分。但是,对于任何非平凡的应用程序,您都需要至少通过自签名程序来签署您的jar文件,即使此自签名并未提供任何实际的额外安全性。使用Java开发工具包中提供的工具,任何人都可以自行签署应用程序。自签名证书适用于开发工作,但每次运行应用程序时,您都必须点击风险接受复选框。
好的,我的应用程序不平凡,我需要对我的jar文件进行签名。该怎么做?
这里是快速答案:这是一个两步过程。您首先使用keytool程序创建所需的凭据,然后使用jarsigner工具签署您的jar文件。您只需要偶尔创建凭据,但需要为每个部署的jar文件进行签名。
要创建这些凭据(自签名证书),请使用:
$JAVA_HOME/bin/keytool -genkeypair -keyalg RSA -keysize 2048 -alias mydomain -validity 1825
这会在你的主目录中创建一个名为
.keystore
的证书,有效期为五年。你需要回答它的提示,我使用"password"作为密码。由于我只将此证书用于自签署JAR文件,所以安全性不是很大问题。有效期参数指定了证书的有效期(以天计算)。
每次更新JAR文件时,您都需要对其进行签名。假设您在分发目录中,并且需要签名
applet.jar
,请使用以下命令:
$JAVA_HOME/bin/jarsigner -tsa http://timestamp.digicert.com -storepass password applet.jar mydomain
-storepass
后的“password”与您在keytool中使用的密码相匹配,“mydomain”与keytool
-alias
参数相匹配。您需要指定 -tsa(时间戳机构)参数,
http://timestamp.digicert.com 是(或至少曾经是)一个公开可用的时间戳机构。我不知道时间戳机构具体是做什么的或者为什么需要它,但是 jarsigner 没有默认值且没有直接记录如何查找时间戳机构,因此无法正常工作。
您现在可以使用或忽略以下批处理文件。我创建它是因为当我需要创建新证书(原始证书已过期)时,我忘记了如何创建。希望下次我们需要它时能够找到这个批处理文件,也许是五年后。
#!/bin/bash
if [ -z "$JAVA_HOME" ] ; then
. setupJAVA
if [ -z "$JAVA_HOME" ] ; then
echo "JAVA_HOME environment variable missing"
exit 1
fi
fi
if [ -z "$HOMEPATH" ] ; then
echo "HOMEPATH environment variable missing"
echo "Try export HOMEPATH=\Users\myname"
exit 1
fi
home_path=`cygpath --path --unix C:$HOMEPATH`
PGM=$JAVA_HOME/bin/keytool
if [ ! -x "$PGM" ] ; then
echo "$PGM not executable"
exit 1
fi
set -x
rm -Rf $home_path/.keystore
$PGM -genkeypair -keyalg RSA -keysize 2048 -alias mydomain -validity 1825
exit $?
注意:我的设置JAVA脚本设置了JAVA_HOME
环境变量。对于Linux,请使用$HOME
代替$HOMEPATH
并跳过cygpath
部分。这些在Cygwin环境中转换Linux和Windows文件名格式。
每次安装JAR文件时,您需要对其进行签名。为了自动化此过程,我修改了我的Makefile以执行此操作。以下是我使用的make代码片段:
.PHONY: golfer.install
golfer.install: test golfer
: (Not relevant to discussion)
cp -p $(OBJDIR)/usr/fne/golfer/Applet/applet.jar $(DEPLOYDIR)/webapps/golfer/.
jarsigner -tsa http://timestamp.digicert.com -storepass password "$(shell cygpath --path --windows "$(DEPLOYDIR)/webapps/golfer/applet.jar")" mydomain
: (Not relevant to discussion)
$(OBDIR)
和$(DEPLOYDIR)
变量与此讨论无关。它们是在我的Makefile构建环境中设置的目录路径。
如何将小程序迁移到新的JNLP环境?
现在我们有了自签名的jar文件,接下来我们可以开始想办法运行它们。许多浏览器不再支持NPAPI,因此<applet>
标记将无法工作。deployJava.runApplet()也不能使用。我不会介绍为什么要放弃对NPAPI的支持,只会介绍需要做什么才能使您的现有应用程序运行。
我发现迁移代码最大的问题是,最终我必须创建.jnlp文件而不是.html文件。我将向您展示如何执行此操作,并描述我修改和添加的代码。
以下是我用来生成HTML的(现已过时)JavaScript代码:
var out;
function appHead(title,cname,height,width)
{
var todoWindow= window.open('','','');
out= todoWindow.document;
out.write('<html>');
out.write('<head><title>' + title + '</title></head>');
out.write('<body>\n');
out.write('<applet code="' + cname + '.class"');
out.write(' codebase="./"')
out.write(' archive="applet.jar,jars/common.jar"');
out.write(' width="' + width + '" height="' + height + '">\n');
}
function appParm(name, value)
{
out.write(' <param-name="' + name + '" value="' + value + '"/>\n');
}
function appTail()
{
out.write('Your browser is completely ignoring the <APPLET> tag!\n');
out.write('</applet>');
out.write('<form>');
out.write('<input type="button" value="Done" onclick="window.close()">');
out.write('</form>');
out.write('</body>');
out.write('</html>');
out.close();
out= null;
}
function cardEvents(eventsID, obj)
{
if( obj.selectedIndex == 0 )
{
alert("No date selected");
return;
}
appHead('Score card', 'EventsCard', '100%', '100%');
appParm('events-nick', eventsID);
appParm('events-date', obj[obj.selectedIndex].value);
appTail();
reset();
}
我们不需要看到由我的servlet生成的包含调用cardEvents函数的表单按钮的html。它类似于“完成”按钮生成,不需要进行更改。
应该很容易将此JavaScript转换为生成jnlp文件。这是不可能的,或者至少我找不到任何可以工作的示例,并且无法通过修改任何不完整的示例来找到一种方法。即使我只想生成jnlp xml,“window.open()”语句也会始终添加“”和“”部分。我还尝试了“document.open("application / x-java-jnlp-file")”。即使指定了mime类型,仍然存在不必要的html和body部分。
我找到的所有文档都没有显示如何动态生成所需的.jnlp文件,其中包括用户选择的applet参数。这是我代替使用的解决方法。
我用以下代码替换了applet.js中的html生成:
var out;
function appHead(title,cname,height,width)
{
out= cname + ',' + title;
}
function appParm(name, value)
{
out= out + ',' + name + '=' + value;
}
function appTail()
{
var specs= 'menubar=yes,toolbar=yes';
window.open('Applet.jnlp?' + out, '_self', specs);
}
function cardEvents(eventsID, obj)
{
}
这将生成一个URL,格式为Applet.jnlp,className,description,parm=value,parm=value,...
。
然后我创建了一个名为AppletServlet.java的新Servlet。传递给它的URL提供了生成.jnlp文件所需的所有信息。此代码遵循标准示例Servlet结构,其中调用doGet来处理请求。以下是代码:
public void
doGet( // Handle HTTP "GET" request
HttpServletRequest req, // Request information
HttpServletResponse res)
throws ServletException, IOException
{
String q= req.getQueryString();
if( debug ) log("doGet("+q+")");
res.setContentType("text/html");
query(req, res);
}
public void
putError( // Generate error response
PrintWriter out, // The response writer
String msg)
{ out.println("<HTML>");
out.println("<HEAD><TITLE>" + msg + "</TITLE></HEAD>");
out.println("<BODY>");
out.println("<H1 align=\"center\">" + msg + "</H1>");
out.println("</BODY>");
out.println("</HTML>");
}
protected void
query( // Handle a query
HttpServletRequest req, // Request information
HttpServletResponse res)
throws ServletException, IOException
{
String q= req.getQueryString();
if( debug ) log("query("+q+")");
PrintWriter out = res.getWriter();
String BOGUS= "<br> Malformed request: query: '" + q + "'";
int index= q.indexOf(',');
if( index < 0 || index == (q.length() - 1) )
{
putError(out, BOGUS);
return;
}
String invoke= q.substring(0, index);
q= q.substring(index+1);
index= q.indexOf(',');
if( index < 0 )
index= q.length();
String title= q.substring(0, index);
title= java.net.URLDecoder.decode(title, "UTF-8");
Vector<String> param= new Vector<String>();
if( index < q.length() )
{
q= q.substring(index+1);
for(;;)
{
index= q.indexOf(',');
if( index < 0 )
index= q.length();
String s= q.substring(0, index);
int x= s.indexOf('=');
if( x < 0 )
{
putError(out, BOGUS);
return;
}
param.add(s);
if( index >= q.length() )
break;
q= q.substring(index+1);
}
}
res.setContentType("application/x-java-jnlp-file");
out.println("<?xml version='1.0' encoding='utf-8'?>");
out.println("<jnlp spec='1.0+' codebase='http://localhost:8080/golfer'>");
out.println(" <information>");
out.println(" <title>" + title + "</title>");
out.println(" <vendor>My Name</vendor>");
out.println(" <description>" + title + "</description>");
out.println(" </information>");
out.println(" <security><all-permissions/></security>");
out.println(" <resources>");
out.println(" <j2se version='1.7+'/>");
out.println(" <jar href='applet.jar'/>");
out.println(" <jar href='jars/common.jar'/>");
out.println(" </resources>");
out.println(" <applet-desc main-class='" + invoke + "' name='" + title + "'" +
" height='90%' width='98%'>");
for(int i= 0; i<param.size(); i++)
{
String s= param.elementAt(i);
int x= s.indexOf('=');
String n= s.substring(0,x);
String v= s.substring(x+1);
out.println(" <param name='" + n+ "' value='" + v + "'/>");
}
out.println(" </applet-desc>");
out.println("</jnlp>");
}
注意事项:
debug
是我的“调试启用”标志,
log()
将调试信息写入stdout。在此新代码版本中,高度和宽度不作为参数传递,而是硬编码。结果发现,在HTML版本中,“100%”始终被用作高度和宽度,并且效果很好。由于某些(我不知道的)原因,当使用100%高度和宽度的.jnlp代码调用时,我的小程序窗口底部和可能右侧被截断。我使用这些新的高度和宽度参数来解决这个格式问题。
为了调用我的新AppletServlet,我修改了我的web.xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Applet</servlet-name>
<servlet-class>usr.fne.golfer.AppletServlet</servlet-class>
<init-param>
<param-name>property-path</param-name>
<param-value>profile</param-value>
</init-param>
<init-param>
<param-name>property-file</param-name>
<param-value>golfer.pro</param-value>
</init-param>
<load-on-startup>30</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Applet</servlet-name>
<url-pattern>/Applet.jnlp</url-pattern>
</servlet-mapping>
: (Other Servlets unchanged)
</web-app>
这会导致任何Applet.jnlp URL都会调用AppletServlet。浏览器会忽略查询字符串,并将结果视为文件名为Applet.jnlp。
为了使操作更加顺畅,您需要设置Windows文件关联,以便.jnlp
文件调用Java(TM) Web Start Launcher。在Windows中,您的JWS Launcher是C:\Program Files\java\jre*\bin\javaws.exe
(使用最新的jre文件夹)。此外,如果您使用Chrome,您的下载目录将包含生成的Applet.jnlp文件。您需要定期清理它们。
这完成了迁移过程。没有applet受到影响(或更改),因此30,000行源代码的大部分保持不变。
虽然我从操作代码中剪切和粘贴创建示例,但可能会出现拼写错误。如果发现任何不正确、缺失或不明确之处,请进行评论。