項(xiàng)目中有時(shí)會(huì)用到自動(dòng)補(bǔ)全查詢,就像Google搜索框、淘寶商品搜索功能,輸入漢字或字母,則以該漢字或字母開(kāi)頭的相關(guān)條目會(huì)顯示出來(lái)供用戶選擇, autocomplete插件就是完成這樣的功能。
autocomplete官網(wǎng) : http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/ (可下載jQuery autocomplete插件)。
淘寶商品搜索功能 效果:

下面來(lái)使用 autocomplete插件來(lái)實(shí)現(xiàn)類似效果。
1. 創(chuàng)建 AjaxPage.aspx 頁(yè)面,在其中定義 WebMethod 方法來(lái)返回 搜索頁(yè)面需要的輸入框所有提示條目。 后臺(tái)代碼如下:
1 using System.Collections.Generic;
2 using System.IO;
3 using System.Runtime.Serialization.Json;
4 using System.Web.Services;
5
6 public partial class AjaxPage : System.Web.UI.Page
7 {
8 [WebMethod]
9 public static string GetAllHints()
10 {
11 Dictionary<string, string> data = new Dictionary<string, string>();
12 data.Add("蘋(píng)果4代iphone正品", "21782");
13 data.Add("蘋(píng)果4代 手機(jī)套", "238061");
14 data.Add("蘋(píng)果4", "838360");
15 data.Add("蘋(píng)果皮", "242721");
16 data.Add("蘋(píng)果筆記本", "63348");
17 data.Add("蘋(píng)果4s", "24030");
18 data.Add("戴爾筆記本", "110105");
19 data.Add("戴爾手機(jī)", "18870");
20 data.Add("戴爾鍵盤(pán)", "30367");
21
22 DataContractJsonSerializer serializer = new DataContractJsonSerializer(data.GetType());
23
24 using (MemoryStream ms = new MemoryStream())
25 {
26 serializer.WriteObject(ms, data);
27 return System.Text.Encoding.UTF8.GetString(ms.ToArray());
28 }
29 }
30 }
注:該方法返回的數(shù)據(jù)格式為json字符串。
2. 創(chuàng)建搜索頁(yè)面 Index.aspx, 前臺(tái)代碼如下:

1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Index.aspx.cs" Inherits="_Default" %>
2
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4
5 <html xmlns="http://www.w3.org/1999/xhtml">
6 <head runat="server">
7 <title></title>
8 <link rel="Stylesheet" href="Styles/jquery.autocomplete.css" />
9 <script type="text/javascript" src="Scripts/jquery-1.4.1.js"></script>
10 <script type="text/javascript" src="Scripts/jquery.autocomplete.js"></script>
11 <script type="text/javascript">
12 var v = 1;
13 $(document).ready(function () {
14 $.ajax({
15 type: "POST",
16 contentType: "application/json",
17 url: "AjaxPage.aspx/GetAllHints",
18 data: "{}",
19 dataType: "json",
20 success: function (msg) {
21 var datas = eval('(' + msg.d + ')');
22 $("#txtIput").autocomplete(datas, {
23 formatItem: function (row, i, max) {
24 return "<table width='400px'><tr><td align='left'>" + row.Key + "</td><td align='right'><font style='color: #009933; font-family: 黑體; font-style: italic'>約" + row.Value + "個(gè)寶貝</font> </td></tr></table>";
25 },
26 formatMatch: function(row, i, max){
27 return row.Key;
28 }
29 });
30 }
31 });
32 });
33 </script>
34 </head>
35 <body>
36 <form id="form1" runat="server">
37 <div>
38 <center>
39 <asp:TextBox ID="txtIput" runat="server" Width="400px"></asp:TextBox>
40 </center>
41 </div>
42 </form>
43 </body>
44 </html>

實(shí)現(xiàn)效果如下:

3. autocomplete 參數(shù)說(shuō)明
* minChars (Number)
在觸發(fā)autoComplete前用戶至少需要輸入的字符數(shù).Default: 1,如果設(shè)為0,在輸入框內(nèi)雙擊或者刪除輸入框內(nèi)內(nèi)容時(shí)顯示列表
* width (Number)
指定下拉框的寬度. Default: input元素的寬度
* max (Number)
autoComplete下拉顯示項(xiàng)目的個(gè)數(shù).Default: 10
* delay (Number)
擊鍵后激活autoComplete的延遲時(shí)間(單位毫秒).Default: 遠(yuǎn)程為400 本地10
* autoFill (Boolean)
要不要在用戶選擇時(shí)自動(dòng)將用戶當(dāng)前鼠標(biāo)所在的值填入到input框. Default: false
* mustMatch (Booolean)
如果設(shè)置為true,autoComplete只會(huì)允許匹配的結(jié)果出現(xiàn)在輸入框,所有當(dāng)用戶輸入的是非法字符時(shí)將會(huì)得不到下拉框.Default: false
* matchContains (Boolean)
決定比較時(shí)是否要在字符串內(nèi)部查看匹配,如ba是否與foo bar中的ba匹配.使用緩存時(shí)比較重要.不要和autofill混用.Default: false
* selectFirst (Boolean)
如果設(shè)置成true,在用戶鍵入tab或return鍵時(shí)autoComplete下拉列表的第一個(gè)值將被自動(dòng)選擇,盡管它沒(méi)被手工選中(用鍵盤(pán)或鼠標(biāo)).當(dāng)然如果用戶選中某個(gè)項(xiàng)目,那么就用用戶選中的值. Default: true
* cacheLength (Number)
緩存的長(zhǎng)度.即對(duì)從數(shù)據(jù)庫(kù)中取到的結(jié)果集要緩存多少條記錄.設(shè)成1為不緩存.Default: 10
* matchSubset (Boolean)
autoComplete可不可以使用對(duì)服務(wù)器查詢的緩存,如果緩存對(duì)foo的查詢結(jié)果,那么如果用戶輸入foo就不需要再進(jìn)行檢索了,直接使用緩存.通常是打開(kāi)這個(gè)選項(xiàng)以減輕服務(wù)器的負(fù)擔(dān)以提高性能.只會(huì)在緩存長(zhǎng)度大于1時(shí)有效.Default: true
* matchCase (Boolean)
比較是否開(kāi)啟大小寫(xiě)敏感開(kāi)關(guān).使用緩存時(shí)比較重要.如果你理解上一個(gè)選項(xiàng),這個(gè)也就不難理解,就好比f(wàn)oot要不要到FOO的緩存中去找.Default: false
* multiple (Boolean)
是否允許輸入多個(gè)值即多次使用autoComplete以輸入多個(gè)值. Default: false
* multipleSeparator (String)
如果是多選時(shí),用來(lái)分開(kāi)各個(gè)選擇的字符. Default: ","
* scroll (Boolean)
當(dāng)結(jié)果集大于默認(rèn)高度時(shí)是否使用卷軸顯示 Default: true
* scrollHeight (Number)
自動(dòng)完成提示的卷軸高度用像素大小表示 Default: 180
* formatItem (Function)
為每個(gè)要顯示的項(xiàng)目使用高級(jí)標(biāo)簽.即對(duì)結(jié)果中的每一行都會(huì)調(diào)用這個(gè)函數(shù),返回值將用LI元素包含顯示在下拉列表中. Autocompleter會(huì)提供三個(gè)參數(shù)(row, i, max): 返回的結(jié)果數(shù)組, 當(dāng)前處理的行數(shù)(即第幾個(gè)項(xiàng)目,是從1開(kāi)始的自然數(shù)), 當(dāng)前結(jié)果數(shù)組元素的個(gè)數(shù)即項(xiàng)目的個(gè)數(shù). Default: none, 表示不指定自定義的處理函數(shù),這樣下拉列表中的每一行只包含一個(gè)值.
* formatResult (Function)
和formatItem類似,但可以將將要輸入到input文本框內(nèi)的值進(jìn)行格式化.同樣有三個(gè)參數(shù),和formatItem一樣.Default: none,表示要么是只有數(shù)據(jù),要么是使用formatItem提供的值.
* formatMatch (Function)
對(duì)每一行數(shù)據(jù)使用此函數(shù)格式化需要查詢的數(shù)據(jù)格式. 返回值是給內(nèi)部搜索算法使用的. 參數(shù)值row
* extraParams (Object)
為后臺(tái)(一般是服務(wù)端的腳本)提供更多的參數(shù).和通常的作法一樣是使用一個(gè)鍵值對(duì)對(duì)象.如果傳過(guò)去的值是{ bar:4 },將會(huì)被autocompleter解析成my_autocomplete_backend.php?q=foo&bar=4 (假設(shè)當(dāng)前用戶輸入了foo). Default: {}
* result (handler)
此事件會(huì)在用戶選中某一項(xiàng)后觸發(fā),參數(shù)為:
event: 事件對(duì)象. event.type為result.
data: 選中的數(shù)據(jù)行.
formatted:formatResult函數(shù)返回的值
例如:
$("#singleBirdRemote").result(function(event, data, formatted) {
//如選擇后給其他控件賦值,觸發(fā)別的事件等等
});
過(guò)頁(yè)頭生成Token,進(jìn)行請(qǐng)求驗(yàn)證,解決Ajax請(qǐng)求安全問(wèn)題。目前為止我做的最多的防止ajax請(qǐng)求攻擊的就是添加驗(yàn)證碼、添加隨機(jī)Token,限制同一請(qǐng)求在規(guī)定時(shí)間內(nèi)的最大請(qǐng)求數(shù)。
下面重點(diǎn)說(shuō)說(shuō)添加隨機(jī)Token限制:
token是為了防止表單重復(fù)提交,token 原理大致為:
1:顯示表單的那個(gè) action 中使用 createToken() 生成一個(gè)隨機(jī)的 token值,并存放在服務(wù)端(session或者cache中),并且傳遞一份到頁(yè)面中
2:表單頁(yè)面使用一個(gè)隱藏表單域獲取后端傳過(guò)來(lái)的 token值,該表單頁(yè)面提交時(shí)會(huì)將此 token 值一同提交到后端
3:在表單頁(yè)面提交到的 actioin 中使用 validateToken() 將服務(wù)端與表單隱藏域中的 token 值進(jìn)行對(duì)比,如果服務(wù)端存在 token值并且與表單提交過(guò)來(lái)的值相等,證明是第一次提交。
4:每次校驗(yàn)過(guò)后服務(wù)端的 token 值會(huì)立即被清除,所以當(dāng)用戶重復(fù)提交時(shí),后面的提交校驗(yàn)都再也無(wú)法通過(guò)。從而實(shí)現(xiàn)了防止重復(fù)提交的功能,validateToken 是在 synchronized 塊中執(zhí)行的保障了多線程下的安全性。
token 會(huì)優(yōu)先存入 me.setTokenCache(ITokenCache) 指定的 TokenCache 中,如果未指定則默認(rèn)使用 session 來(lái)存放
但是這種機(jī)制是有問(wèn)題的,比如我是用ajax提交表單,提交完成以后表單頁(yè)面并不刷新,然后我修改了部分?jǐn)?shù)據(jù)以后再次提交頁(yè)面,那么token還是之前的那個(gè)token,后臺(tái)會(huì)以為這個(gè)為重復(fù)提交不能通過(guò)校驗(yàn),那么請(qǐng)求就不能完成,數(shù)據(jù)無(wú)法得到正確的處理。我認(rèn)為合理的機(jī)制應(yīng)該是這樣的:
1:顯示表單的那個(gè) action 中使用 createToken() 生成一個(gè)隨機(jī)的 token值,并且傳遞一份到頁(yè)面中
2:表單頁(yè)面使用一個(gè)隱藏表單域獲取后端傳過(guò)來(lái)的 token值,該表單頁(yè)面提交時(shí)會(huì)將此 token 值一同提交到后端
3:將提交過(guò)來(lái)的token值放入session或者cache中,然后執(zhí)行controller中的代碼,代碼全部執(zhí)行完以后,再把存入session或cache中的token值刪除掉;驗(yàn)證用戶是否為重復(fù)提交只需要驗(yàn)證提交過(guò)來(lái)的token是否存在于session或cache中,有則為重復(fù)提交,無(wú)則為正常提交。
4:該邏輯應(yīng)該可以寫(xiě)成一個(gè)Interceptor,在需要的地方加上,或者直接設(shè)為全局?jǐn)r截器都是可以的,簡(jiǎn)單,快捷;
JDK1.6版添加了新的ScriptEngine類,允許用戶直接執(zhí)行js代碼。
在Java中直接調(diào)用js代碼
不能調(diào)用瀏覽器中定義的js函數(shù),會(huì)拋出異常提示ReferenceError: “alert” is not defined。
| package com.sinaapp.manjushri; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; /** * 直接調(diào)用js代碼 */ public class ScriptEngineTest { public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("javascript"); try{ engine.eval("var a=3; var b=4;print (a+b);"); // engine.eval("alert(\"js alert\");"); // 不能調(diào)用瀏覽器中定義的js函數(shù) // 錯(cuò)誤,會(huì)拋出alert引用不存在的異常 }catch(ScriptException e){ e.printStackTrace(); } } } |
輸出結(jié)果:7
在Java中綁定js變量
在調(diào)用engine.get(key);時(shí),如果key沒(méi)有定義,則返回null
| package com.sinaapp.manjushri; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class ScriptEngineTest2 { public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("javascript"); engine.put("a", 4); engine.put("b", 3); Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); try { // 只能為Double,使用Float和Integer會(huì)拋出異常 Double result = (Double) engine.eval("a+b"); System.out.println("result = " + result); engine.eval("c=a+b"); Double c = (Double)engine.get("c"); System.out.println("c = " + c); } catch (ScriptException e) { e.printStackTrace(); } } } |
輸出:
result = 7.0
c = 7.0
在Java中調(diào)用js文件中的function,傳入調(diào)用參數(shù),并獲取返回值
js文件中的merge函數(shù)將兩個(gè)參數(shù)a,b相加,并返回c。
| // expression.js function merge(a, b) { c = a * b; return c; } |
在Java代碼中讀取js文件,并參數(shù)兩個(gè)參數(shù),然后回去返回值。
| package com.sinaapp.manjushri; import java.io.FileReader; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; /** * Java調(diào)用并執(zhí)行js文件,傳遞參數(shù),并活動(dòng)返回值 * * @author manjushri */ public class ScriptEngineTest { public static void main(String[] args) throws Exception { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("javascript"); String jsFileName = "expression.js"; // 讀取js文件 FileReader reader = new FileReader(jsFileName); // 執(zhí)行指定腳本 engine.eval(reader); if(engine instanceof Invocable) { Invocable invoke = (Invocable)engine; // 調(diào)用merge方法,并傳入兩個(gè)參數(shù) // c = merge(2, 3); Double c = (Double)invoke.invokeFunction("merge", 2, 3); System.out.println("c = " + c); } reader.close(); } } |
輸出結(jié)果:
c = 5.0
java調(diào)用腳本語(yǔ)言筆記(jython,jruby,groovy)
有兩種方法
1.java se 6以后實(shí)現(xiàn)了jsr 223規(guī)范
java代碼:
- ScriptEngineManager factory = new ScriptEngineManager();
- ScriptEngineManager scriptEngine = factory.getEngineByName("javascript");//或者"js"
- scriptEngine.eval(code);//執(zhí)行一段腳本,code是js代碼
很方便調(diào)用腳本
2.可以使用腳本語(yǔ)方本身提供的與java的集成手段
jython集成
使用jsr223:
前提下載jython的包,已實(shí)現(xiàn)jsr223
(建議在官網(wǎng)上下載,在安裝目錄下有jython.jar,http://repo2.maven.org/maven2/org/python/jython/2.5.0/ 這里也有,但是這個(gè)包里沒(méi)有jsr223的實(shí)現(xiàn),看包下存不存在org.python.jsr223)
- ScriptEngineManager factory = new ScriptEngineManager();
- ScriptEngineManager scriptEngine = factory.getEngineByName("python");//或者"jython"
- scriptEngine.eval(code);
使用PythonInterpreter,可以調(diào)用exec(String code)方法:
- PythonInterpreter interpreter = new PythonInterpreter();
- interpreter.exec(code);
訪問(wèn)數(shù)據(jù)庫(kù)
使用jdbc:
- from oracle.jdbc.driver import OracleDriver
- from java.sql import DriverManager
-
- username = 'hr'
- password = '123456'
- url = 'jdbc:oracle:thin:@localhost:1521:XE'
- driver = OracleDriver()
- DriverManager.registerDriver(driver)
- conn = DriverManager.getConnection(url, username, password)
- stmt = conn.createStatement()
- sql = "select salary from EMPLOYEES t where t.salary<2300"
- rs = stmt.executeQuery(sql)
- while (rs.next()):
- print rs.getInt('salary')
- rs.close()
- stmt.close()
結(jié)果:
2200
2100
2200
使用zxJDBC :
- from com.ziclix.python.sql import zxJDBC
-
- url = 'jdbc:oracle:thin:@localhost:1521:XE'
- username = 'hr'
- password = '123456'
- driverName = 'oracle.jdbc.driver.OracleDriver'
- mysqlConn = zxJDBC.connect(url,username, password,driverName)
- cursor = mysqlConn.cursor()
- cursor.execute("select last_name from EMPLOYEES t where t.salary<2300");
- #print cursor.fetchone()
- list = cursor.fetchall()
- for record in list:
- print "name:"+record[0]
- #print cursor.description[0]
- #print cursor.description[1]
結(jié)果:
name:麥克
name:Olson
name:Philtanker
從數(shù)據(jù)庫(kù)中查出的中文內(nèi)容正常的。
而在代碼里面的中文全部是亂碼或拋異常,未解決。
與jruby集成
使用jsr223:Java代碼
- ScriptEngineManager factory = new ScriptEngineManager();
- ScriptEngineManager scriptEngine = factory.getEngineByName("jruby");//或者"ruby"
- scriptEngine.eval(code);
訪問(wèn)數(shù)據(jù)庫(kù)
Ruby代碼
- require 'java'
-
- module JavaLang
- include_package "java.lang"
- end
-
- module JavaSql
- include_package 'java.sql'
- end
-
- begin
- username = 'hr'
- password = '123456'
- url = 'jdbc:oracle:thin:@localhost:1521:XE'
- driverName = 'oracle.jdbc.driver.OracleDriver'
- JavaLang::Class.forName(driverName).newInstance
- conn = JavaSql::DriverManager.getConnection(url, username, password)
- stmt = conn.createStatement
- sql = "select last_name from EMPLOYEES t where t.salary<2300"
- rs = stmt.executeQuery(sql)
- while (rs.next) do
- puts "名字:"+rs.getString("last_name")
- end
- rs.close
- stmt.close
- conn.close()
- rescue JavaLang::ClassNotFoundException
- puts "ClassNotFoundException"
- rescue JavaSql::SQLException
- puts "SQLException"
- end
結(jié)果:
名字:楹﹀厠
名字:Olson
名字:Philtanker
從數(shù)據(jù)庫(kù)中查出的中文內(nèi)容為亂碼的。
而在代碼里面的中文正常。
與groovy集成
使用jsr223:
Java代碼
- ScriptEngineManager factory = new ScriptEngineManager();
- ScriptEngineManager scriptEngine = factory.getEngineByName("groovy");//或者"Groovy"
- scriptEngine.eval(code);
使用GroovyShell:
Java代碼
- GroovyShell shell = new GroovyShell();
- Script script = shell.parse(code);
- Object result = script.run();
訪問(wèn)數(shù)據(jù)庫(kù)
- import groovy.sql.Sql
-
- def username = 'hr'
- def password = '123456'
- def url = 'jdbc:oracle:thin:@localhost:1521:XE'
- def driverName = 'oracle.jdbc.driver.OracleDriver'
- def sql = Sql.newInstance(url, username, password, driverName)
-
- sql.eachRow("select last_name from EMPLOYEES t where t.salary<2300") {
- println "名字:${it.last_name}"
- }
結(jié)果:
名字:麥克
名字:Olson
名字:Philtanker
在使用groovy過(guò)程中碰到了一個(gè)異常
Exception in thread "main" java.lang.VerifyError: (class: groovy/runtime/metaclass/java/util/ArrayListMetaClass, method: super$2$invokeMethod signature: (Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;ZZ)Ljava/lang/Object;) Illegal use of nonvirtual function call
這個(gè)異常解決花了很長(zhǎng)時(shí)間
是因?yàn)樵谠瓉?lái)項(xiàng)目中存在json-lib-2.1.jar(有可能名稱為json-lib-2.1-jdk15.jar),這個(gè)包是用來(lái)處理json的,與groovy1.7.5存在沖突,更新為json-lib-2.3.jar即可
(json-lib里有一些groovy運(yùn)行時(shí)處理的內(nèi)容)
這兩天Java服務(wù)器上忽然遇到這樣的異常:
avax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
問(wèn)題的根本是:
缺少安全證書(shū)時(shí)出現(xiàn)的異常。
解決問(wèn)題方法:
將你要訪問(wèn)的webservice/url....的安全認(rèn)證證書(shū)導(dǎo)入到客戶端即可。
以下是獲取安全證書(shū)的一種方法,通過(guò)以下程序獲取安全證書(shū):
/*
* Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Sun Microsystems nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class InstallCert {
public static void main(String[] args)
throws Exception {
String host;
int port;
char[] passphrase;
if ((args.length == 1) || (args.length == 2)) {
String[] c = args[0].split(":");
host = c[0];
port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
String p = (args.length == 1) ? "changeit" : args[1];
passphrase = p.toCharArray();
}
else {
System.out
.println("Usage: java InstallCert <host>[:port] [passphrase]");
return;
}
File file =
new File("jssecacerts");
if (file.isFile() ==
false) {
char SEP = File.separatorChar;
File dir =
new File(System.getProperty("java.home") + SEP + "lib"
+ SEP + "security");
file =
new File(dir, "jssecacerts");
if (file.isFile() ==
false) {
file =
new File(dir, "cacerts");
}
}
System.out.println("Loading KeyStore " + file + "

");
InputStream in =
new FileInputStream(file);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(in, passphrase);
in.close();
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf
.getTrustManagers()[0];
SavingTrustManager tm =
new SavingTrustManager(defaultTrustManager);
context.init(
null,
new TrustManager[] { tm },
null);
SSLSocketFactory factory = context.getSocketFactory();
System.out
.println("Opening connection to " + host + ":" + port + "

");
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.setSoTimeout(10000);
try {
System.out.println("Starting SSL handshake

");
socket.startHandshake();
socket.close();
System.out.println();
System.out.println("No errors, certificate is already trusted");
}
catch (SSLException e) {
System.out.println();
e.printStackTrace(System.out);
}
X509Certificate[] chain = tm.chain;
if (chain ==
null) {
System.out.println("Could not obtain server certificate chain");
return;
}
BufferedReader reader =
new BufferedReader(
new InputStreamReader(
System.in));
System.out.println();
System.out.println("Server sent " + chain.length + " certificate(s):");
System.out.println();
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (
int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
System.out.println(" " + (i + 1) + " Subject "
+ cert.getSubjectDN());
System.out.println(" Issuer " + cert.getIssuerDN());
sha1.update(cert.getEncoded());
System.out.println(" sha1 " + toHexString(sha1.digest()));
md5.update(cert.getEncoded());
System.out.println(" md5 " + toHexString(md5.digest()));
System.out.println();
}
System.out
.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
String line = reader.readLine().trim();
int k;
try {
k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
}
catch (NumberFormatException e) {
System.out.println("KeyStore not changed");
return;
}
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
ks.setCertificateEntry(alias, cert);
OutputStream out =
new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
System.out.println();
System.out.println(cert);
System.out.println();
System.out
.println("Added certificate to keystore 'jssecacerts' using alias '"
+ alias + "'");
}
private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
private static String toHexString(
byte[] bytes) {
StringBuilder sb =
new StringBuilder(bytes.length * 3);
for (
int b : bytes) {
b &= 0xff;
sb.append(HEXDIGITS[b >> 4]);
sb.append(HEXDIGITS[b & 15]);
sb.append(' ');
}
return sb.toString();
}
private static class SavingTrustManager
implements X509TrustManager {
private final X509TrustManager tm;
private X509Certificate[] chain;
SavingTrustManager(X509TrustManager tm) {
this.tm = tm;
}
public X509Certificate[] getAcceptedIssuers() {
throw new UnsupportedOperationException();
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new UnsupportedOperationException();
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
this.chain = chain;
tm.checkServerTrusted(chain, authType);
}
}
}
編譯InstallCert.java,然后執(zhí)行:java InstallCert hostname,比如:
java InstallCert www.twitter.com
會(huì)看到如下信息:
java InstallCert www.twitter.com
Loading KeyStore /usr/java/jdk1.6.0_16/jre/lib/security/cacerts

Opening connection to www.twitter.com:443

Starting SSL handshake

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1476)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:174)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:168)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:846)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:106)
at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:495)
at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:433)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:815)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1025)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1038)
at InstallCert.main(InstallCert.java:63)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:221)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:145)
at sun.security.validator.Validator.validate(Validator.java:203)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:172)
at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:158)
at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(SSLContextImpl.java:320)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:839)

7 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:236)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:194)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:216)

13 more
Server sent 2 certificate(s):
1 Subject CN=www.twitter.com, O=example.com, C=US
Issuer CN=Certificate Shack, O=example.com, C=US
sha1 2e 7f 76 9b 52 91 09 2e 5d 8f 6b 61 39 2d 5e 06 e4 d8 e9 c7
md5 dd d1 a8 03 d7 6c 4b 11 a7 3d 74 28 89 d0 67 54
2 Subject CN=Certificate Shack, O=example.com, C=US
Issuer CN=Certificate Shack, O=example.com, C=US
sha1 fb 58 a7 03 c4 4e 3b 0e e3 2c 40 2f 87 64 13 4d df e1 a1 a6
md5 72 a0 95 43 7e 41 88 18 ae 2f 6d 98 01 2c 89 68
Enter certificate to add to trusted keystore or 'q' to quit: [1]
輸入1,回車,然后會(huì)在當(dāng)前的目錄下產(chǎn)生一個(gè)名為“ssecacerts”的證書(shū)。
將證書(shū)拷貝到$JAVA_HOME/jre/lib/security目錄下,或者通過(guò)以下方式:
System.setProperty("javax.net.ssl.trustStore", "你的jssecacerts證書(shū)路徑");
注意:因?yàn)槭庆o態(tài)加載,所以要重新啟動(dòng)你的Web Server,證書(shū)才能生效。
試了以上的方法,后來(lái)發(fā)現(xiàn)還不行。最后突然心血來(lái)潮:我把Myeclipse關(guān)閉,直接啟動(dòng)Tomcat,然后運(yùn)行,居然就可以了。具體原因沒(méi)有找到。估計(jì)是
我的Myeclipse引用的JDK引用不對(duì)。后來(lái)就沒(méi)有具體找原因了。