介紹「安全圖章」
網路釣魚(Phishing),指的是「以偽造的網頁騙取使用者輸入帳號密碼」的駭客行為。Yahoo! 採用的安全圖章(Sign-in Seal)則是一套很有效的反制網路釣魚(Anti-phishing)系統,而且,饒富創意。
以使用者的觀點來看,Yahoo! 安全圖章大致上的作法是:
- 使用者先輸入四個中文字製作出安全圖章(或者,也可以上傳一張圖片當作安全圖章),Yahoo! 網站「記住」這台電腦。
- 從此,只要這台電腦的使用者連上 Yahoo! 登入頁,Yahoo! 網站會「認出」這台電腦,然後秀出約定的圖片。
- 假如安全圖章消失,就可能是連到了駭客仿造的 Yahoo! 登入頁。
安全圖章設定完成後,單純清除 Cookie 將無法刪除安全圖章,不禁叫人好奇它運作的原理。
這篇文章將說明如何製作一套類似於 Yahoo! 安全圖章(Sign-in Seal)的系統,內容包括:識別 Client 端電腦的各項作法與原理、如何以 Flash Shared Object 存取檔案、ASP.NET 如何以 GDI+ 繪製圖片等問題。
如何識別 Client 端電腦
嗯!這是關鍵,如果不能有效識別出究竟是哪一部電腦連上我們的網站,接下來當然無法秀出對應的圖片。
IP 位址?不行,很多電腦都是從 DHCP 伺服器取得的浮動 IP,會變來變去的東西顯然不是理想的識別信物。
網卡序號
網卡序號(MAC Address)是很理想的識別資訊,不過,JavaScript 拿不到 OSI 第二層那麼低階的資訊,宣稱拿得到網卡序號的 JavaScript(例如下列程式),其實都是透過元件進行,並非 JavaScript 原本就有的內建功能。由於是透過 ActiveX 元件,現在的瀏覽器對於來自 Web 含有 new ActiveXObject("Prog.ID") 的 JavaScript 早有警覺,恐怕無法順利建立元件完成任務。我們要另覓途徑。
<html>
<head>
<script type="text/javascript">
<!--
function btnListMacAddress_Click() {
// Using SWbemLocator object to retrieve information of network adapters.
var obj = new ActiveXObject("WbemScripting.SWbemLocator");
var svr = obj.ConnectServer(".");
var info = svr.ExecQuery("select * from Win32_NetworkAdapterConfiguration");
var items = new Enumerator(info);
// Listing MAC address
document.write("<table border=1>");
for ( ; !items.atEnd(); items.moveNext()) {
var item = items.item();
document.write("<tr>");
document.write("<td>" + item.Caption + "</td>");
document.write("<td>" + item.MACAddress + "</td>");
document.write("</tr>");
}
document.write("</table>");
}
//-->
</script>
</head>
<body>
<INPUT id="btnListMacAddress" type="button"
value="List MAC Address" onclick="btnListMacAddress_Click()">
</body>
</html>
document.write("<table border=1>");
for ( ; !items.atEnd(); items.moveNext()) {
var item = items.item();
document.write("<tr>");
document.write("<td>" + item.Caption + "</td>");
document.write("<td>" + item.MACAddress + "</td>");
document.write("</tr>");
}
document.write("</table>");
}
//-->
</script>
</head>
<body>
<INPUT id="btnListMacAddress" type="button"
value="List MAC Address" onclick="btnListMacAddress_Click()">
</body>
</html>
Cookie、userData、sessionStorage
如果不容易從 Client 端取得電腦的獨特資訊,改由 Server 端傳送一段指紋碼到 Client 端呢?日後,Server 端便可根據這段識別碼來辨別電腦。在這方面,Cookie 是最為普及的簡易作法,但是,清除 Cookie 是很容易的事(以 IE 6 為例:工具 | 網際網路選項 | 刪除 Cookie)。
如果識別碼太容易消失,使用者恐怕得一再地重新設定安全圖章,如此一來,將大幅降低使用者對於安全圖章的信心與使用意願。嗯!得找一個使用者不知道或者不容易清除的方式來寫入識別碼。
IE 瀏覽器從 IE 5 開始提供的 userData Behavior,可供開發人員在 Client 端的 UserData 資料夾讀寫 XML 文件。以 Windows 2003 為例,在預設情況下,以下的程式(引用自微軟 MSDN 網站 http://msdn.microsoft.com/en-us/library/ms531424.aspx)將產生下列檔案:
C:\Documents and Settings\使用者帳號\UserData\亂數字串\oXMLBranch[1].xml
<HTML>
<HEAD>
<STYLE>
.storeuserData {behavior:url(#default#userData);}
</STYLE>
<SCRIPT>
<HEAD>
<STYLE>
.storeuserData {behavior:url(#default#userData);}
</STYLE>
<SCRIPT>
function fnSaveInput(){
var oPersist=oPersistForm.oPersistInput;
oPersist.setAttribute("sPersist",oPersist.value);
oPersist.save("oXMLBranch");
}
function fnLoadInput(){
var oPersist=oPersistForm.oPersistInput;
oPersist.load("oXMLBranch");
oPersist.value=oPersist.getAttribute("sPersist");
}
</SCRIPT>
</HEAD>
<BODY>
<FORM ID="oPersistForm">
<INPUT CLASS="storeuserData" TYPE="text" ID="oPersistInput">
<INPUT TYPE="button" VALUE="Load" onclick="fnLoadInput()">
<INPUT TYPE="button" VALUE="Save" onclick="fnSaveInput()">
</FORM>
</BODY>
</HTML>
看來,我們似乎是找到了一個簡單可用的、安靜的識別方式。不過,IE 7 [工具 | 刪除歷程記錄 | 全部刪除],還是簡單清掉這些 XML 檔案,況且,userData 僅限 IE 瀏覽器專用。
此外,FireFox 從 2.0 版開始也提供一個類似的 sessionStorage 於同一個連線階段在 Client 端暫存資料:
// FireFox 2.0 以上
if (window.sessionStorage) {
sessionStorage.setItem(key, value);
...
}
// FireFox 2.0 以上
if (window.sessionStorage) {
sessionStorage.setItem(key, value);
...
}
Flash Shared Object
經過上述的討論,大致上有個輪廓了,我們要找的是一個符合下列條件的解決方案:
- 普遍的(不限作業系統、瀏覽器)。
- 安靜的(不會被瀏覽器拒絕或者攔下來要求同意執行)。
- 存活率高的(一般使用者不知道位置或者不容易刪除)。
- 安靜的(不會被瀏覽器拒絕或者攔下來要求同意執行)。
- 存活率高的(一般使用者不知道位置或者不容易刪除)。
當然啦!這個動作要經過使用者同意才能進行,畢竟,那是他(她)的電腦。
利用 Flash Shared Object 讀寫 .sol 檔,大致能符合上述條件。Flash Player 是一個極為普遍的元件,各種作業系統、瀏覽器乃至手機、PDA等裝置都可以找到它的蹤跡,其 ActionScript 讓開發人員得以寫程式整合 JavaScript 或者透過網路與伺服端通訊。
在 Flash 執行下列程式會產生附檔名為 .sol 的 Flash Cookie:(沒錯,只有三行,十分簡單)
var fso = SharedObject.getLocal("FSO");
fso.data.machineID = "電腦識別碼";
fso.flush();
var fso = SharedObject.getLocal("FSO");
fso.data.machineID = "電腦識別碼";
fso.flush();
如果您使用的是 WinXP 或 Windows 2003,在預設情況下,FSO.sol 的檔名(含路徑)如下:
C:\Documents and Settings\帳號\Application Data\Macromedia\Flash Player\#SharedObjects\亂數\網址\檔名.swf\FSO.sol
C:\Documents and Settings\帳號\Application Data\Macromedia\Flash Player\#SharedObjects\亂數\網址\檔名.swf\FSO.sol
讀取 Flash Cookie 的程式也同樣簡單,如下:
var fso = SharedObject.getLocal("FSO");
sTest = fso.data.machineID;
var fso = SharedObject.getLocal("FSO");
sTest = fso.data.machineID;
系統架構與概念
找到識別方法後,該來想想其他的環節要如何進行了。請先按照數字 1 到 9 瀏覽一下這張圖片,對於理解接下來的文章內容應該會有幫助。(註:點按圖片可放大圖片)
整合 Flash 與 JavaScript
好了,現在我們已經知道要如何寫入 Flash Cookie 了,那麼,要寫什麼內容呢?我的意思是說,該怎麼通知 Flash 物件寫些什麼呢?作法之一是:利用 FlashVars 傳變數給 Flash 物件。
舉例來說,SignInSeal.swf 是我們製作完成的 Flash 影片,通常會利用下列的 <object> 元素將其嵌入網頁:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="1" height="1" id="SignInSeal" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="SignInSeal.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="SignInSeal.swf" quality="high" bgcolor="#ffffff" width="1"
height="1" name="SignInSeal" align="middle"
allowScriptAccess="sameDomain" type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>
</object>
現在,只要將以下這列加進去(例如倒數第三行的位置):
<param name="FlashVars" value="actionID=1&machineID=671990" />
<param name="FlashVars" value="actionID=1&machineID=671990" />
如此,便可將 671990 代入 machineID,進而指定給 fso.data.machineID,最後寫入 fso.sol 檔案。
var fso = SharedObject.getLocal("FSO");
fso.data.machineID = machineID; // 671990
fso.flush();
var fso = SharedObject.getLocal("FSO");
fso.data.machineID = machineID; // 671990
fso.flush();
此外,提醒您一點,上述的 allowScriptAccess="sameDomain" 是必須額外加進去的屬性。
OK,寫入的問題解決了。接下來與之相對的問題是:Flash 物件讀到的電腦識別碼要怎麼傳出來呢?
在 ActionScript,我們可以利用 getURL() 執行到網頁上頭的 JavaScript。於是,以「登入頁」為例,可按下列步驟將圖片秀出來:
1. 登入頁的 FlashVars 改成:
<param name="FlashVars"
value="actionID=3&imageID=Image1&imageURL=GetSigninSealImage.aspx" />
<param name="FlashVars"
value="actionID=3&imageID=Image1&imageURL=GetSigninSealImage.aspx" />
2. 以 Shared Object 讀出電腦識別碼
3. 呼叫 getURL("javascript:setImageSource(參數值1, 參數值2, ...);");
4. setImageSource() 是我們寫的 JavaScript 函式,
負責改變圖片的 .src 屬性 = GetSigninSealImage.aspx?machineID=671990
負責改變圖片的 .src 屬性 = GetSigninSealImage.aspx?machineID=671990
5. 最後,GetSigninSealImage.aspx 查出 671990 電腦識別編號所約定的印記,繪出圖片。
以 GDI+ 繪製圖片
.NET 產生圖檔大致有以下三個步驟:
1. 取得 Graphics 物件,Graphics 物件相當於一張「畫布」。
2. 利用 Graphics.DrawXXX() 或 Graphics.FillXXX() 等方法作畫,過程中,
會運用到畫筆(Pen)、畫刷(Brush)、字型(Font)等物件。
會運用到畫筆(Pen)、畫刷(Brush)、字型(Font)等物件。
3. 輸出圖片。
' 圖片的預設尺寸
Private _Width As Integer = 200
Private _Height As Integer = 40
Private _Width As Integer = 200
Private _Height As Integer = 40
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Dim ContentString As String = "問渠哪得清如許" ' GetContentString()
Dim BitmapImage As New Bitmap(_Width, _Height, PixelFormat.Format32bppArgb)
Dim Canvas As Graphics = Graphics.FromImage(BitmapImage)
' 好了,現在我們取得 Graphics,在這張畫布畫圖,相當於畫在 Bitmap 上
Dim BitmapImage As New Bitmap(_Width, _Height, PixelFormat.Format32bppArgb)
Dim Canvas As Graphics = Graphics.FromImage(BitmapImage)
' 好了,現在我們取得 Graphics,在這張畫布畫圖,相當於畫在 Bitmap 上
' 填入漸層藍色當背景
Dim ClientArea As New Rectangle(New Point(0, 0), New Size(_Width, _Height))
Dim BlueGradientBrush As New LinearGradientBrush( _
Dim ClientArea As New Rectangle(New Point(0, 0), New Size(_Width, _Height))
Dim BlueGradientBrush As New LinearGradientBrush( _
New Point(0, 0), New Point(0, _Height), Color.White, Color.RoyalBlue)
Canvas.FillRectangle(BlueGradientBrush, ClientArea)
Canvas.FillRectangle(BlueGradientBrush, ClientArea)
' 畫出邊框
Dim BluePen As New Pen(Color.Blue, 2)
Canvas.DrawRectangle(BluePen, ClientArea)
Dim BluePen As New Pen(Color.Blue, 2)
Canvas.DrawRectangle(BluePen, ClientArea)
' 以標楷體 18 點斜體字樣秀出文字
Dim SF As New StringFormat(StringFormatFlags.NoWrap Or _
Dim SF As New StringFormat(StringFormatFlags.NoWrap Or _
StringFormatFlags.NoFontFallback)
SF.LineAlignment = StringAlignment.Near
SF.Alignment = StringAlignment.Center
Dim F As Font = New Font("標楷體", 18, FontStyle.Italic)
ClientArea.Offset(0, 4)
Canvas.DrawString(ContentString, F, Brushes.Black, _
SF.LineAlignment = StringAlignment.Near
SF.Alignment = StringAlignment.Center
Dim F As Font = New Font("標楷體", 18, FontStyle.Italic)
ClientArea.Offset(0, 4)
Canvas.DrawString(ContentString, F, Brushes.Black, _
CType(ClientArea, RectangleF), SF)
ClientArea.Offset(0, -4)
ClientArea.Offset(0, -4)
' 再裝飾一段底線文字
Canvas.FillRectangle(Brushes.Black, 0, 30, _Width, 10)
SF.LineAlignment = StringAlignment.Far
SF.Alignment = StringAlignment.Center
F = New Font("Arial", 8, FontStyle.Italic)
Canvas.DrawString("Anti-phishing with Sign-in Seal", F, Brushes.White, _
Canvas.FillRectangle(Brushes.Black, 0, 30, _Width, 10)
SF.LineAlignment = StringAlignment.Far
SF.Alignment = StringAlignment.Center
F = New Font("Arial", 8, FontStyle.Italic)
Canvas.DrawString("Anti-phishing with Sign-in Seal", F, Brushes.White, _
CType(ClientArea, RectangleF), SF)
' 輸出圖片到 Client 端
Response.Clear()
Response.ContentType = "image/jpeg"
BitmapImage.Save(Response.OutputStream, ImageFormat.Jpeg)
Response.End()
End Sub
Response.Clear()
Response.ContentType = "image/jpeg"
BitmapImage.Save(Response.OutputStream, ImageFormat.Jpeg)
Response.End()
End Sub
親自動手做一遍
幾個關鍵技術都已逐一說明,讓我們從頭到尾動手整個做一遍吧!加油!
工作環境
以下的內容,均按照我的工作環境為例進行說明,如下:
- Windows Server 2003
- SQL Server 2005
- Visual Studio 2005 Professional
- Windows Server 2003
- SQL Server 2005
- Visual Studio 2005 Professional
- Flash MX 2004
製作 SignInSeal.swf
1. 在 Flash MX 2004 新增一個 Flash 文件
2. 滑鼠點按第一個影格,在「動作 - 影格」輸入程式,內容如下:
var fso = SharedObject.getLocal("FSO");
if (actionID == 3) {
// Set source url for "Sign-in Seal" image
sCmd = "javascript:setImageSource(
'" + imageID + "' , '" + imageURL + "' , '" + fso.data.machineID + "')"
getURL(sCmd);
}
else if (actionID == 1) {
// Write Flash Shared Object to store cookie
fso.data.machineID = machineID;
fso.flush();
}
else if (actionID == 2) {
// delete Shared Object file
fso.clear();
}
// Set source url for "Sign-in Seal" image
sCmd = "javascript:setImageSource(
'" + imageID + "' , '" + imageURL + "' , '" + fso.data.machineID + "')"
getURL(sCmd);
}
else if (actionID == 1) {
// Write Flash Shared Object to store cookie
fso.data.machineID = machineID;
fso.flush();
}
else if (actionID == 2) {
// delete Shared Object file
fso.clear();
}
3. Flash 文件的大小設定成寬度高度各一個像素。
4. 儲存檔案,檔案名稱:SignInSeal.fla
5. 發佈,預設情況下會產生 SignInSeal.swf、SignInSeal.html。
文末附上的範例檔案內含一套製作完成的 SignInSeal.fla 與 SignInSeal.swf,歡迎自由取用。
設定資料庫
連接 SQL Server 2005,執行下列 SQL Script 設定稍後將用來儲存「安全圖章」的資料庫 :
-- 建立 SigninSealDB 資料庫
CREATE DATABASE SigninSealDB
GO
CREATE DATABASE SigninSealDB
GO
USE SigninSealDB
GO
GO
-- 建立 SigninSealDB 資料表, 用來儲存安全印記
-- MachineID: 電腦識別碼
-- SignSealText: 使用者約定的印記文字
-- LastAccess: 最後存取日期, 閒置過久將予刪除
CREATE TABLE SigninSeal
(
SignSealID INT IDENTITY NOT NULL PRIMARY KEY,
MachineID VARCHAR(20) NOT NULL,
SignSealText NVarChar(20) NOT NULL,
LastAccess DateTime DEFAULT GetDate()
)
GO
-- MachineID: 電腦識別碼
-- SignSealText: 使用者約定的印記文字
-- LastAccess: 最後存取日期, 閒置過久將予刪除
CREATE TABLE SigninSeal
(
SignSealID INT IDENTITY NOT NULL PRIMARY KEY,
MachineID VARCHAR(20) NOT NULL,
SignSealText NVarChar(20) NOT NULL,
LastAccess DateTime DEFAULT GetDate()
)
GO
-- 呼叫 usp_NewSigninSeal 可新增一筆安全印記
CREATE PROCEDURE usp_NewSigninSeal
(
@MachineID VARCHAR(20),
@SignSealText NVARCHAR(20)
)
AS
SET NOCOUNT ON
INSERT INTO SigninSeal (MachineID, SignSealText)
VALUES (@MachineID, @SignSealText)
GO
CREATE PROCEDURE usp_NewSigninSeal
(
@MachineID VARCHAR(20),
@SignSealText NVARCHAR(20)
)
AS
SET NOCOUNT ON
INSERT INTO SigninSeal (MachineID, SignSealText)
VALUES (@MachineID, @SignSealText)
GO
-- usp_GetSignSealText 預存程序用來查出特定識別碼
-- 對應的安全印記文字, 並且更新最後讀取日期
CREATE PROCEDURE usp_GetSignSealText
(
@MachineID VARCHAR(20),
@SignSealText NVARCHAR(20) OUTPUT
)
AS
SET NOCOUNT ON
SELECT @SignSealText = SignSealText
FROM SigninSeal
WHERE MachineID = @MachineID
IF @SignSealText IS NULL
SET @SignSealText = N'請啟動安全印記'
-- 對應的安全印記文字, 並且更新最後讀取日期
CREATE PROCEDURE usp_GetSignSealText
(
@MachineID VARCHAR(20),
@SignSealText NVARCHAR(20) OUTPUT
)
AS
SET NOCOUNT ON
SELECT @SignSealText = SignSealText
FROM SigninSeal
WHERE MachineID = @MachineID
IF @SignSealText IS NULL
SET @SignSealText = N'請啟動安全印記'
UPDATE SigninSeal SET LastAccess = GetDate()
WHERE MachineID = @MachineID
GO
WHERE MachineID = @MachineID
GO
-- 採用 Windows 整合認證的話, ASP.NET 2.0 預設以
-- NETWORK SERVICE 帳號連接 SQL Server, 請確認一
-- 下這個帳號是否已是 LoginID
--
-- 以下指令將 NETWORK SERVICE 這個 LoginID 設定成
-- SigninSealDB 資料庫的使用者
CREATE USER [NETWORK SERVICE]
FOR LOGIN [NT AUTHORITY\NETWORK SERVICE]
GO
-- NETWORK SERVICE 帳號連接 SQL Server, 請確認一
-- 下這個帳號是否已是 LoginID
--
-- 以下指令將 NETWORK SERVICE 這個 LoginID 設定成
-- SigninSealDB 資料庫的使用者
CREATE USER [NETWORK SERVICE]
FOR LOGIN [NT AUTHORITY\NETWORK SERVICE]
GO
-- 授予 NETWORK SERVICE 可以執行上述兩個預存程序
GRANT EXECUTE ON usp_NewSigninSeal TO [NETWORK SERVICE]
GRANT EXECUTE ON usp_GetSignSealText TO [NETWORK SERVICE]
GO
GRANT EXECUTE ON usp_NewSigninSeal TO [NETWORK SERVICE]
GRANT EXECUTE ON usp_GetSignSealText TO [NETWORK SERVICE]
GO
建立 ASP.NET 網站
1. 建立一個新 ASP.NET 網站,名稱:SigninSealLab
2. 滑鼠右鍵 SigninSealLab 網站 | 加入現有項目,選擇之前設計好的 SignInSeal.swf
3. 開啟 Web.config,將原本的 <connectionStrings /> 連線字串改成:
<connectionStrings>
<add name="cn" connectionString=
"Data Source=(local);Initial Catalog=SigninSealDB;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<connectionStrings>
<add name="cn" connectionString=
"Data Source=(local);Initial Catalog=SigninSealDB;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
4. 除了原本就有的 Default.aspx,再新增三個 Web Form,名稱分別是:
GetSigninSealImage.aspx
Login.aspx
GetSigninSealImage.aspx
Login.aspx
MakeSigninSeal.aspx
FlashCookie.txt(Flash 與 JavaScript)
滑鼠右鍵 SigninSealLab 網站,新增一項項目,類型選擇「文字檔」,檔案名稱:FlashCookie.txt。
這個檔案稍後會以下列型式的方式置入 MakeSigninSeal.aspx 與 Login.aspx:
<!-- #include virtual="FlashCookie.txt" -->
<!-- #include virtual="FlashCookie.txt" -->
FlashCookie.txt 的內容如下:
<script language="javascript" type="text/javascript">
// <!CDATA[
// <!CDATA[
function setImageSource(imageID, imageURL, machineID) {
document.getElementById(imageID).src = imageURL + "?machineID=" + machineID;
}
document.getElementById(imageID).src = imageURL + "?machineID=" + machineID;
}
// ]]>
</script>
</script>
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="1" height="1" id="SignInSeal" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="SignInSeal.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="FlashVars" value="<%=FlashVars%>" />
<embed src="SignInSeal.swf" quality="high" bgcolor="#ffffff" width="1" height="1" name="SignInSeal" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="SignInSeal.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<param name="FlashVars" value="<%=FlashVars%>" />
<embed src="SignInSeal.swf" quality="high" bgcolor="#ffffff" width="1" height="1" name="SignInSeal" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>
關於<%=FlashVars%>:稍後我們的程式會指定適當的內容,以便傳遞參數到 Client 端的 Flash 物件。
GetSigninSealImage.aspx
GetSigninSealImage.aspx 用來繪製、輸出圖片,不必管操作畫面,直接將 GetSigninSealImage.aspx.vb 寫成:
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Data.SqlClient
Imports System.Web.Configuration
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Data.SqlClient
Imports System.Web.Configuration
Partial Class GetSigninSealImage
Inherits System.Web.UI.Page
Inherits System.Web.UI.Page
' 圖片的預設尺寸
Private _Width As Integer = 200
Private _Height As Integer = 40
Private _Width As Integer = 200
Private _Height As Integer = 40
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Dim ContentString As String = GetContentString()
Dim BitmapImage As New Bitmap(_Width, _Height, PixelFormat.Format32bppArgb)
Dim Canvas As Graphics = Graphics.FromImage(BitmapImage)
' 好了,現在我們取得 Graphics,在這張畫布畫圖,相當於畫在 Bitmap 上
Dim ContentString As String = GetContentString()
Dim BitmapImage As New Bitmap(_Width, _Height, PixelFormat.Format32bppArgb)
Dim Canvas As Graphics = Graphics.FromImage(BitmapImage)
' 好了,現在我們取得 Graphics,在這張畫布畫圖,相當於畫在 Bitmap 上
' 填入漸層藍色當背景
Dim ClientArea As New Rectangle(New Point(0, 0), New Size(_Width, _Height))
Dim BlueGradientBrush As New LinearGradientBrush(New Point(0, 0), _
Dim ClientArea As New Rectangle(New Point(0, 0), New Size(_Width, _Height))
Dim BlueGradientBrush As New LinearGradientBrush(New Point(0, 0), _
New Point(0, _Height), Color.White, Color.RoyalBlue)
Canvas.FillRectangle(BlueGradientBrush, ClientArea)
Canvas.FillRectangle(BlueGradientBrush, ClientArea)
' 畫出邊框
Dim BluePen As New Pen(Color.Blue, 2)
Canvas.DrawRectangle(BluePen, ClientArea)
Dim BluePen As New Pen(Color.Blue, 2)
Canvas.DrawRectangle(BluePen, ClientArea)
' 以標楷體 18 點斜體字樣秀出文字
Dim SF As New StringFormat(StringFormatFlags.NoWrap Or _
Dim SF As New StringFormat(StringFormatFlags.NoWrap Or _
StringFormatFlags.NoFontFallback)
SF.LineAlignment = StringAlignment.Near
SF.Alignment = StringAlignment.Center
Dim F As Font = New Font("標楷體", 18, FontStyle.Italic)
ClientArea.Offset(0, 4)
Canvas.DrawString(ContentString, F, Brushes.Black, _
SF.LineAlignment = StringAlignment.Near
SF.Alignment = StringAlignment.Center
Dim F As Font = New Font("標楷體", 18, FontStyle.Italic)
ClientArea.Offset(0, 4)
Canvas.DrawString(ContentString, F, Brushes.Black, _
CType(ClientArea, RectangleF), SF)
ClientArea.Offset(0, -4)
ClientArea.Offset(0, -4)
' 再裝飾一段底線文字
Canvas.FillRectangle(Brushes.Black, 0, 30, _Width, 10)
SF.LineAlignment = StringAlignment.Far
SF.Alignment = StringAlignment.Center
F = New Font("Arial", 8, FontStyle.Italic)
Canvas.DrawString("Anti-phishing with Sign-in Seal", F, _
Canvas.FillRectangle(Brushes.Black, 0, 30, _Width, 10)
SF.LineAlignment = StringAlignment.Far
SF.Alignment = StringAlignment.Center
F = New Font("Arial", 8, FontStyle.Italic)
Canvas.DrawString("Anti-phishing with Sign-in Seal", F, _
Brushes.White, CType(ClientArea, RectangleF), SF)
' 輸出圖片到 Client 端
Response.Clear()
Response.ContentType = "image/jpeg"
BitmapImage.Save(Response.OutputStream, ImageFormat.Jpeg)
Response.End()
End Sub
Response.Clear()
Response.ContentType = "image/jpeg"
BitmapImage.Save(Response.OutputStream, ImageFormat.Jpeg)
Response.End()
End Sub
Function GetContentString() As String
Dim Result As String = "請啟動安全印記"
Dim Result As String = "請啟動安全印記"
' 預覽圖片時, 顯示 TextContent 的文字內容
If Request.QueryString("TextContent") IsNot Nothing Then
Result = Request.QueryString("TextContent")
Return Result
End If
If Request.QueryString("TextContent") IsNot Nothing Then
Result = Request.QueryString("TextContent")
Return Result
End If
' 這個 Cookie 先前應該已由 Lognin.aspx 寫到 Client 端
' 如果現在讀不到這個 Cookie, 就要小心是駭客仿造的網頁
If Request.Cookies("SealCheckKey") Is Nothing Then
Return Result
End If
' 如果現在讀不到這個 Cookie, 就要小心是駭客仿造的網頁
If Request.Cookies("SealCheckKey") Is Nothing Then
Return Result
End If
Dim MachineID As String = Request.QueryString("machineID")
If MachineID = "" Then
Return Result
End If
If MachineID = "" Then
Return Result
End If
' ------------------------------------------------------------
' 呼叫 usp_GetSignSealText 預存程序查出約定的安全印記文字
' ------------------------------------------------------------
' 從 Web.config 讀取連線字串
Dim _ConnectionString = _
WebConfigurationManager.ConnectionStrings("cn").ConnectionString
Dim cn As New SqlConnection(_ConnectionString)
cn.Open() ' 連接 SQL 2005
' 呼叫 usp_GetSignSealText 預存程序查出約定的安全印記文字
' ------------------------------------------------------------
' 從 Web.config 讀取連線字串
Dim _ConnectionString = _
WebConfigurationManager.ConnectionStrings("cn").ConnectionString
Dim cn As New SqlConnection(_ConnectionString)
cn.Open() ' 連接 SQL 2005
' 呼叫 usp_GetSignSealText 預存程序
Dim cmd As New SqlCommand("usp_GetSignSealText", cn)
cmd.CommandType = Data.CommandType.StoredProcedure
cmd.Parameters.Add("@MachineID", Data.SqlDbType.VarChar, 20).Value = MachineID
cmd.Parameters.Add("@SignSealText", _
Dim cmd As New SqlCommand("usp_GetSignSealText", cn)
cmd.CommandType = Data.CommandType.StoredProcedure
cmd.Parameters.Add("@MachineID", Data.SqlDbType.VarChar, 20).Value = MachineID
cmd.Parameters.Add("@SignSealText", _
Data.SqlDbType.NVarChar, 20).Direction = Data.ParameterDirection.Output
cmd.ExecuteNonQuery()
Result = cmd.Parameters("@SignSealText").Value ' 安全印記文字
cn.Close()
cmd.ExecuteNonQuery()
Result = cmd.Parameters("@SignSealText").Value ' 安全印記文字
cn.Close()
Return Result
End Function
End Function
End Class
MakeSigninSeal.aspx
MakeSigninSeal.aspx 提供介面讓使用者設定、預覽、刪除安全印記。請按照下圖畫面設計使用者操作介面:
完成上述操作畫面之後,請直接在 MakeSigninSeal.aspx 的 </form> 標籤前一列加上這列:
<!-- #include virtual="FlashCookie.txt" -->
MakeSigninSeal.aspx.vb 的程式如下:
Imports System.Data.SqlClient
Imports System.Web.Configuration
Imports System.Web.Configuration
Partial Class MakeSigninSeal
Inherits System.Web.UI.Page
Inherits System.Web.UI.Page
' 變數值會由 FlashCookie.txt 的 <%=FlashVars%> 輸出到 Client 端
Friend FlashVars As String = ""
Friend FlashVars As String = ""
Protected Sub PreviewButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles PreviewButton.Click
Image1.Visible = True
' 圖片來源網址設定成 GetSigninSealImage.aspx
' 預覽 TextContent 指定的文字內容
Image1.ImageUrl = "GetSigninSealImage.aspx?TextContent=" + _
Server.UrlEncode(TextBox1.Text)
End Sub
Image1.Visible = True
' 圖片來源網址設定成 GetSigninSealImage.aspx
' 預覽 TextContent 指定的文字內容
Image1.ImageUrl = "GetSigninSealImage.aspx?TextContent=" + _
Server.UrlEncode(TextBox1.Text)
End Sub
Protected Sub StoreButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles StoreButton.Click
' 隨機產生長度 20 的電腦序號
Dim Dice As New System.Random
Dim MachineID As String = ""
For i As Integer = 1 To 20
MachineID += Dice.Next(0, 10).ToString
Next
' 隨機產生長度 20 的電腦序號
Dim Dice As New System.Random
Dim MachineID As String = ""
For i As Integer = 1 To 20
MachineID += Dice.Next(0, 10).ToString
Next
' 呼叫 usp_NewSigninSeal 預存程序新增一筆安全印記
Dim _ConnectionString = _
WebConfigurationManager.ConnectionStrings("cn").ConnectionString
Dim cn As New SqlConnection(_ConnectionString)
cn.Open()
Dim cmd As New SqlCommand("usp_NewSigninSeal", cn)
cmd.CommandType = Data.CommandType.StoredProcedure
cmd.Parameters.Add("@MachineID", Data.SqlDbType.VarChar, 20).Value = MachineID
cmd.Parameters.Add("@SignSealText", _
Dim _ConnectionString = _
WebConfigurationManager.ConnectionStrings("cn").ConnectionString
Dim cn As New SqlConnection(_ConnectionString)
cn.Open()
Dim cmd As New SqlCommand("usp_NewSigninSeal", cn)
cmd.CommandType = Data.CommandType.StoredProcedure
cmd.Parameters.Add("@MachineID", Data.SqlDbType.VarChar, 20).Value = MachineID
cmd.Parameters.Add("@SignSealText", _
Data.SqlDbType.NVarChar, 20).Value = TextBox1.Text
cmd.ExecuteNonQuery()
cn.Close()
cmd.ExecuteNonQuery()
cn.Close()
' 以 actionID=1 通知 SignInSeal.swf 寫入電腦識別序號
FlashVars = "actionID=1&machineID=" + MachineID
FlashVars = "actionID=1&machineID=" + MachineID
' 不使用 Response.Redirect() 重導, 因為要先讓 SignInSeal.swf
' 完成寫入 Flash Cookie 之後, 才重導回首頁
Me.ClientScript.RegisterStartupScript(Me.GetType, "", _
"<meta http-equiv='refresh' content='1;URL=Default.aspx'>")
End Sub
' 完成寫入 Flash Cookie 之後, 才重導回首頁
Me.ClientScript.RegisterStartupScript(Me.GetType, "", _
"<meta http-equiv='refresh' content='1;URL=Default.aspx'>")
End Sub
Protected Sub DeleteImageButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles DeleteImageButton.Click
' 以 actionID=2 通知 SignInSeal.swf 刪除電腦識別序號
FlashVars = "actionID=2"
' 重導回首頁
Me.ClientScript.RegisterStartupScript(Me.GetType, "", _
"<meta http-equiv='refresh' content='1;URL=Default.aspx'>")
End Sub
End Class
' 以 actionID=2 通知 SignInSeal.swf 刪除電腦識別序號
FlashVars = "actionID=2"
' 重導回首頁
Me.ClientScript.RegisterStartupScript(Me.GetType, "", _
"<meta http-equiv='refresh' content='1;URL=Default.aspx'>")
End Sub
End Class
Login.aspx
Login.aspx 是登入頁,應該要秀出先前與使用者約定的安全印記。請按照下圖畫面設計使用者操作介面:
完成上述操作畫面之後,同樣也請直接在 Login.aspx 的 </form> 標籤前一列加上這列:
<!-- #include virtual="FlashCookie.txt" -->
<!-- #include virtual="FlashCookie.txt" -->
Login.aspx.vb 的程式如下:
Imports System.Data.SqlClient
Partial Class Login
Inherits System.Web.UI.Page
Inherits System.Web.UI.Page
' 變數值會由 FlashCookie.txt 的 <%=FlashVars%> 輸出到 Client 端
Friend FlashVars As String = ""
Friend FlashVars As String = ""
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
' 以 actionID=3 通知 SignInSeal.swf 讀出電腦識別序號
' 我們的圖片 ID 是 Image1
FlashVars = "actionID=3&imageID=Image1&imageURL=GetSigninSealImage.aspx"
' 以 actionID=3 通知 SignInSeal.swf 讀出電腦識別序號
' 我們的圖片 ID 是 Image1
FlashVars = "actionID=3&imageID=Image1&imageURL=GetSigninSealImage.aspx"
' 多寫一個 Cookie 防止駭客仿造網頁
' GetSigninSealImage.aspx 繪圖的程式會讀這個 Cookie 再做確認
Response.Cookies("SealCheckKey").Value = System.Guid.NewGuid.ToString
' HttpCookie.HttpOnly = True, 不讓 JavaScript 存取這個 Cookie
Response.Cookies("DoubleCheckKey").HttpOnly = True
End Sub
' GetSigninSealImage.aspx 繪圖的程式會讀這個 Cookie 再做確認
Response.Cookies("SealCheckKey").Value = System.Guid.NewGuid.ToString
' HttpCookie.HttpOnly = True, 不讓 JavaScript 存取這個 Cookie
Response.Cookies("DoubleCheckKey").HttpOnly = True
End Sub
Protected Sub CancelButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles CancelButton.Click
' 回首頁
Response.Redirect("Default.aspx")
End Sub
End Class
' 回首頁
Response.Redirect("Default.aspx")
End Sub
End Class
Default.aspx
最後則是 Default.aspx,放入兩個超連結,分別連往 Login.aspx、MakeSigninSeal.aspx 即可。
完成後的系統執行畫面如下:
下載完整範例程式
1. 點按右側連結下載完整範例程式。
2. 連接 SQL Server 2005 並且執行 Setup_SQL.txt 內的 SQL Script。
3. 開啟 Web.config,修改連線字串,將 (local) 改成您 SQL Server 的名稱。
2. 連接 SQL Server 2005 並且執行 Setup_SQL.txt 內的 SQL Script。
3. 開啟 Web.config,修改連線字串,將 (local) 改成您 SQL Server 的名稱。
錢達智 (Wolfgang Chien)
2008.08.01

















Comments
Write New Comment ▼
Write New Comment
Sorry! This knol's owner(s) have blocked you from editing, making suggestions, or commenting here.