摘要
阅读本文并探索
- 如何突破Web程序无状态性这个让人抓狂的障碍实现自动显示签名结果和批量签名功能。
- 如何将签名功能封装到一个实现了IHttpHandler接口的类库中,使Client端的代码尽可能的简单。
- 使用数字签名API函数需要注意的几个问题。
本文介绍在Web程序中使用数字签名所遇到的特殊困难和解决方法,并给出一个超简单但相当实用的DEMO。
DEMO程序的效果
让我们先来看看实现之后的效果。
让Client端代码尽可能的简单
我们将数字签名操作的复杂性全部封装到一个命名空间为mylib.util.lnca的类库中,类库只暴露一个名为Signer的类。
Signer的Client (本例中的Default.aspx)的职责只有
- 构造一个含有待签名的数据的Dictionary作为Signer的输入,然后调用Signer.do_sign()函数进行数字签名。
- 在页面上放置一个专门用于取得并显示签名结果的按钮,并将这个按钮的ClientID传递给Signer,这样Signer在完成签名后就可以自动触发这个按钮。在将程序发布给最终用户时,要把这个按钮的top属性设为-10000,这样最终用户就看不到这个按钮了。
Default.aspx 的设计视图的截图
Default.aspx 的源代码如下
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>无标题页</title>
</head>
<body>
<form id="form1" runat="server">
<table>
<tr>
<td>
<asp:Button ID="do_sign_button" runat="server" Text="签名" OnClick="do_sign_button_Click" />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label></td>
<td>
<asp:Button ID="show_signed_data_button" runat="server" Text="显示签名结果(自动)" OnClick="show_signed_data_button_Click" /></td>
</tr>
<tr>
<td style="border: solid 1px black; vertical-align: top;">
<asp:GridView ID="sign_candidates_gridview" runat="server" Caption="签名前的数据" CellPadding="4"
ForeColor="#333333" Font-Names="宋体" Font-Size="10pt">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" />
<EditRowStyle BackColor="#7C6F57" />
<SelectedRowStyle BackColor="#C5BBAF" Font-Bold="True" ForeColor="#333333" />
<PagerStyle BackColor="#666666" ForeColor="White" HorizontalAlign="Center" />
<HeaderStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
</td>
<td style="border: solid 1px black; vertical-align: top;">
<asp:GridView ID="signed_data_gridview" runat="server" Caption="签名结果" BackColor="LightGoldenrodYellow"
BorderColor="Tan" BorderWidth="1px" CellPadding="2" ForeColor="Black" OnRowDataBound="signed_data_gridview_RowDataBound" Font-Names="宋体" Font-Size="10pt">
<FooterStyle BackColor="Tan" />
<SelectedRowStyle BackColor="DarkSlateBlue" ForeColor="GhostWhite" />
<PagerStyle BackColor="PaleGoldenrod" ForeColor="DarkSlateBlue" HorizontalAlign="Center" />
<HeaderStyle BackColor="Tan" Font-Bold="True" />
<AlternatingRowStyle BackColor="PaleGoldenrod" />
</asp:GridView>
</td>
</tr>
</table>
</form>
</body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls; using System.Collections.Generic;
using mylib.util.lnca; public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
{
// 这是待签名的数据,保存在一个Dictionary中。Key为数据的ID,Value 为待签名的数据。
Dictionary<string, string> sign_candidates = new Dictionary<string, string>();
sign_candidates.Add("1", "123.45");
sign_candidates.Add("2", "678.90");
sign_candidates.Add("3", "zhf");
sign_candidates.Add("4", "7788");
sign_candidates.Add("5", "1-2-3");
sign_candidates.Add("6", "cnblogs");
sign_candidates_gridview.DataSource
= sign_candidates;sign_candidates_gridview.DataBind();
// 调用 Signer.do_sign() 进行签名
Signer.do_sign(Page, show_signed_data_button.ClientID, sign_candidates);
}
protected void show_signed_data_button_Click(object sender, EventArgs e)
{
if (Signer.error_code == 0) // 签名成功
{
signed_data_gridview.DataSource = Signer.signed_datas;
signed_data_gridview.DataBind();
}
else // 签名失败
{
Label1.Text = Signer.error_message;
}
}
protected void signed_data_gridview_RowDataBound(object sender, GridViewRowEventArgs e)
{
// 每个签名结果的长度都要将近2000个字符,会把GridView撑得很大,为了方便写Blog时截图,我
// 加了一个滚动条,实际作程序时是不需将签名数据显示给用户看的,也就用不着这段代码了。
if (e.Row.RowType == DataControlRowType.DataRow)
{
string content = e.Row.Cells[1].Text;
e.Row.Cells[1].Text = "<div style='overflow: auto; width: 300px; height: 150px;'>" + content + "</div>";
}
}
}
由于Signer是一个HTTP 处理程序,所以需要在Web.config中添加一行对Signer.ashx的注册:
<?xml version="1.0"?>
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<httpHandlers>
<add path="Signer.ashx" verb="*" type=" mylib.util.lnca.Signer, mylib.util.lnca" validate="false"/>
</httpHandlers>
</system.web>
</configuration>
有关HTTP处理程序的创建和应用,可以看《实战 HTTP 处理程序(HTTP Handler)系列》。
由于我们把复杂性都放在了Signer.cs中,Signer.cs的代码有些长,我们会在后面讨论它的几个要点。
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Web;
5 using System.Web.UI;
6 using System.Web.SessionState;
7 using mylib.util.lnca.Properties;
8 using System.Diagnostics;
9
10 namespace mylib.util.lnca
11 {
12 public class Signer : IHttpHandler, IRequiresSessionState
13 {
14 /// <summary>
15 /// Opens a new window to do signing.
16 /// Example
17 /// Dictionary<string, string> sign_candidates = new Dictionary<string, string>();
18 /// sign_candidates.Add("1", "123.45");
19 /// sign_candidates.Add("2", "678.90");
20 /// sign_candidates.Add("3", "zhf");
21 /// sign_candidates.Add("4", "7788");
22 /// sign_candidates.Add("5", "1-2-3");
23 /// sign_candidates.Add("6", "cnblogs");
24 /// Signer.do_sign(Page, on_finished_signing_button.ClientID, sign_candidates);
25 /// </summary>
26 public static void do_sign(Page page, string on_signing_finished_button_client_id,
27 Dictionary<string, string> sign_candidates)
28 {
29 Debug.Assert(sign_candidates != null, "sign_candidates should not be null");
30
31 Signer.sign_candidates = sign_candidates;
32 Signer.sign_candidates_enumerator = Signer.sign_candidates.GetEnumerator();
33 Signer.on_signing_finished_button_client_id = on_signing_finished_button_client_id;
34
35 // initializes Signer.signed_datas
36 Signer.signed_datas = new Dictionary<string, string>();
37 Signer.signing_counter = 0;
38 foreach (string key in sign_candidates.Keys)
39 {
40 Signer.signed_datas.Add(key, "");
41 }
42
43 error_code = 0;
44 error_message = "";
45
46 if (!page.ClientScript.IsStartupScriptRegistered(page.GetType(), "OpenSignerWindow"))
47 {
48 page.ClientScript.RegisterStartupScript(page.GetType(), "OpenSignerWindow",
49 "window.open('Signer.ashx', 'Signer_ashx', 'height=100, alwaysRaised=true, width=200, top=300, left=400, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=no, status=no');", true);
50 }
51 }
52
53 /// <summary>
54 /// Stores error_code during signing.
55 /// </summary>
56 public static long error_code
57 {
58 get { return (long)System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_code"]; }
59 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_code"] = value; }
60 }
61
62 /// <summary>
63 /// Stores error_message during signing.
64 /// </summary>
65 public static string error_message
66 {
67 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_message"] as string; }
68 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.error_message"] = value; }
69 }
70
71 /// <summary>
72 /// Stores datas had be signed.
73 /// </summary>
74 public static Dictionary<string, string> signed_datas
75 {
76 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signed_datas"] as Dictionary<string, string>; }
77 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signed_datas"] = value; }
78 }
79
80 /// <summary>
81 /// The enumerator of sign_candidates
82 /// </summary>
83 private static IEnumerator<KeyValuePair<string, string>> sign_candidates_enumerator
84 {
85 get { return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates_enumerator"] as IEnumerator<KeyValuePair<string, string>>; }
86 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates_enumerator"] = value; }
87 }
88
89 /// <summary>
90 /// Datas needs signing.
91 /// </summary>
92 private static Dictionary<string, string> sign_candidates
93 {
94 get
95 {
96 return System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates"] as Dictionary<string, string>;
97 }
98 set
99 {
100 System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.sign_candidates"] = value;
101 }
102 }
103
104 // Returns the current signing progress.
105 private static int signing_counter
106 {
107 get { return (int)System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signing_counter"]; }
108 set { System.Web.HttpContext.Current.Session["mylib.util.lnca.Signer.signing_counter"] = value; }
109 }
110
111 /// <summary>
112 /// Returns javascript code called doSignData() function at client.
113 /// <param name="sourceData">想要进行签名的数据</param>
114 /// <param name="signAlgo">签名算法,推荐使用SignAlgo.RSA_MD5</param>
115 /// <param name="isAddSignCert">是否在结果中携带证书</param>
116 /// <param name="isAddSrcData">是否在结果中携带原文</param>