Duwamish 7.0 系列分析文章

Duwamish Microsoft 提供一个企业级的分布式系统架构,如果开发企业级的分布式系统,可以模仿这种架构,如果是开发一些简单的系统,则完全可以简化。
 
以前也学习过 Duwamish 范例,只是发现不同时间,不同经历,有不同的体会。正如卢彦所说的一样:通过研究 Duwamish 示例,高手能够领悟到 .Net 应用架构的设计思想,低手能够学习到 .Net 的编程技巧,实在是老少皆宜。
 
因此,这里再次学习并体验一次 Duwamish 范例。
 
1 Duwamish 7.0 结构分为四个逻辑层( FROM MSDN ):
Web Presentation
Web 层为客户端提供对应用程序的访问。这一层是作为 Duwamish.sln 解决方案文件中的 Web 项目实现的。 Web 层由 ASP.NET Web 窗体和代码隐藏文件组成。 Web 窗体只是用 HTML 提供用户操作,而代码隐藏文件实现各种控件的事件处理。
业务外观层 Business Facade
业务外观层为 Web 层提供处理帐户、类别浏览和购书的界面。这一层是作为 Duwamish.sln 解决方案文件中的 BusinessFacade 项目实现的。业务外观层用作隔离层,它将用户界面与各种业务功能的实现隔离开来。除了低级系统和支持功能之外,对数据库服务器的所有调用都是通过此程序集进行的。
业务规则层 Business Rules
业务规则层是作为 Duwamish.sln 解决方案文件中的 BusinessRules 项目实现的,它包含各种业务规则和逻辑的实现。业务规则完成如客户帐户和书籍订单的验证这样的任务。
数据访问层 Data Access
数据访问层为业务规则层提供数据服务。这一层是作为 Duwamish.sln 解决方案文件中的 DataAccess 项目实现的。
 
除了上述四个逻辑层外, Duwamish 7.0 还包含封装在 Duwamish.sln 解决方案文件中的 Common 项目内的共享函数。“通用” (Common) 层包含用于在各层间传递信息的数据集。 Common 项目还包含 Duwamish.sln 解决方案文件中的 SystemFramework 项目内的应用程序配置和跟踪类。
 
2,各个逻辑层之间的关系图(FROM MSDN)及其调用Sequeance 图示例:


下面是 Categories.aspx web 页面获取 Category Description 的整个调用过程。
1 )实例化 ProductSystem 对象
2 )调用 ProductSystem GetCategories() 方法
3 )检测参数的合法性
4 )创建 Categories::DataAccess 对象实例
5 )返回上述对象
6 )调用 Categories::DataAccess 对象的 GetCategories() 方法
7 )创建 CategoryData::Common 对象实例
8 )返回上述对象
9 )返回 CategoryData::Common 对象实例,该实例中已经包含了需要的数据
10 )返回 CategoryData::Common 对象实例给 web/Client
11 )检测数据的合法性
12 )读取并显示结果: Category Description



SystemFramework 项目包含一些 application 需要的配置参数, ApplicationLog 日志类和 ApplicationAssert 参数校验类。 SystemFramework 项目为所有其他的项目所引用。
 
Common 项目包含了用于在各层间传递信息的数据集,如上述的 CategoryData 继承 System.Data.DataSet ,既不是所谓的 typed DataSet ,也不是一般的 DataSet ,不过简单实用,这是基于 .Net Remoting 开发分布式系统用来 tier tier 之间交互数据的一种方法。 Common 项目也被其他的项目引用, SystemFramework 项目除外。
 
BusinessFacade 项目中所有的 Classes 继承 MarshalByRefObject class ,显然是让准备将 BusinessFacade tier 部署为 Remote Objects 。不过,实际上默认这里并没有将其部署为 Remote Objects Web 层仍然调用本地对象(《 Duwamish 部署方案篇 》将分析这个问题)。
 
3 Summary
 
在开发基于 .Net Framework 企业级分布式系统时,上述架构值得推荐,但也并非完美无暇,实际上存在一些值得改进的地方。显然,不可能一个范例适合所有的实际情况么,要求太苛刻了。其实, Enterprise Samples 中的另外一个范例 Fitch and Mather 7.0 ,其架构和 Duwamish 就有些不同了。
 
如果是开发本地的系统,就不要模仿 Duwamish 架构(看看上面获取 Category Description 调用过程就知道了,太费劲。),如 Business Facade Business Rules Classes 应采用 fine-grained interface 设计,层与层之间的交互参数也不必全部采用 DataSet ,适当的时候采用 setter/getter 就可以了,这样不仅可以提高开发效率,而且有助于提高 performance, maintainability and reusability
 
 
References:
2, MSDN, Duwamish
Duwamish 部署方案篇
 
Duwamish 7.0 支持两种多计算机部署方案。非分布式部署方案在一台 Web 主机上部署 Web 层、业务外观、业务规则和数据访问层,但可以在群集间复制该 Web 主机以达到负载平衡。分布式方案在单独的服务器上部署特定的组件。例如,业务外观、业务规则和数据访问层可能位于独立于 Web 主机的服务器上。在实际部署中数据库服务器通常位于单独的计算机上。
 
1, 非分布式部署方案
在一台 Web 主机上部署 Web 层、业务外观、业务规则和数据访问层,然后通过软件(如 Application Center 2000 )或硬件来实现网络场( Web Farm )内各个 Web Server 的负载平衡。
 
在本机默认安装 Duwamish 7.0 时,是采用非分布式部署方案。
  
2, 分布式部署方案
使用 .NET Framework 远程处理技术将应用程序分布到多台计算机中。简单而言,就是 IIS Web Server Application Server 分离,其中 Web 层(包括 SystemFramework Common 项目)部署在 IIS Web 上, BusinessFacde/BusinessRules/DataAccess 层(包括 SystemFramework Common 项目)一起部署在 Application Server 上。
 
 
Duwamish 7.0 使用 HTTP/ 二进制而不是 HTTP/SOAP 。使用 HTTP 的决定基于要通过端口 80 上的防火墙的要求。使用二进制而不是 SOAP 的决定基于性能上的考虑。对于大的数据块,二进制的性能优于 SOAP 。因此,如果要传递大的数据块(例如,数组、数据集或数据表),则使用二进制格式化程序。如果要传递小的数据块,则选择使用 SOAP 还是二进制格式化程序是无关紧要的。传递整数时两者的性能都很好。
 
3, 如何将 Duwamish 7.0 部署为基于 .Net Remoting 的分布式系统
下面采用 Microsoft 提供的 Deploytool 工具自动进行(其实手工也很方便):
C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS>deploytool deploy RemoteMachine=localhost path="C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Duwamish7_Remote" 
command line 窗口输入上述命令行代码。
 
[10/29/2004 6:43:43 AM] Creating directory C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Duwamish7_Remote on W1MIS38
[10/29/2004 6:43:43 AM] Stopping all internet services on W1MIS38
[10/29/2004 6:43:59 AM] Deploying Duwamish7 Business Facade on W1MIS38
[10/29/2004 6:43:59 AM] Creating web site on W1MIS38
[10/29/2004 6:44:00 AM] Generating remoting configuration files
[10/29/2004 6:44:00 AM] Starting all internet services on W1MIS38
[10/29/2004 6:44:02 AM] Starting Default Web Site on W1MIS38
[10/29/2004 6:44:02 AM] Deployment successful
 
运行结果:
1 )在 IIS 创建中创建 Web Application Duwamish7_Facade ),本地路径为: C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Duwamish7_Remote/web 作为 Remote Server 端, Bin 目录下是 BusinessFacde/BusinessRules/DataAccess 层(包括 SystemFramework Common 项目) DLL 文件。
其中 web.config 文件中包含所有 Remote Objects 的配置,如
<wellknown mode="Singleton" type="Duwamish7.BusinessFacade.ProductSystem, Duwamish7.BusinessFacade" objectUri="ProductSystem.rem" />
 
2 Web 层创建 remotingclient.cfg 配置文件,对 Application Server 而言, Web 层相当与 Client 端。
remotingclient.cfg 配置文件中包含 formatter 的设置( binary ),选择二进制格式化程序来序列化消息,注意是出于性能的考虑。
 
3 Web application 加载 remotingclient.cfg 配置文件
Web application global.asax 文件包括如下代码,在 Application_OnStart 事件中加载 Retmoting 配置文件。
void Application_OnStart()
{
ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath ));
string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ),"remotingclient.cfg");
if(File.Exists(configPath))
    RemotingConfiguration.Configure(configPath);
}
 
其中前面代码 ApplicationConfiguration.OnApplicationStart() 是调用 Duwamish7 .SystemFramework .ApplicaitonConfiguration OnApplicationStart() 方法,用来初始化 application root 和读取 web.config 中的配置信息(将在 Duwamish 代码分析篇》 中进行具体分析)。
 
Reference:
1, MSDN, Duwamish 7.0
Duwamish 代码分析篇
 
Written by: Rickie Lee
Nov. 02, 2004
 
继续前面的 2 POST Duwamish 架构分析篇》 Duwamish 部署方案篇》 ,这里在代码层次上分析 Duwamish 7.0 范例,主要目的是解析 Duwamish 范例中值得推荐的编码风格和提炼出可以重用的代码或 Class
 
1 ,读取配置文件类- SystemFramework/ApplicationConfiguration.cs
ApplicationConfiguration 类用来读取 web.config 文件中自定义 section 的配置信息,初始化一些基本设置。
ApplicationConfiguration 类实现 IconfigurationSectionHandler 接口,并需要实现 [C#]
object Create(
   object parent,
   object configContext,
   XmlNode section
) 方法,以分析配置节的 XML 。返回的对象被添加到配置集合中,并通过 GetConfig 访问。
 
 
部分代码片断解释:
1 Code Snippet 1 – ApplicationConfiguration. OnApplicationStart() 方法
public static void OnApplicationStart(String myAppPath)
{
    appRoot = myAppPath;
    System.Configuration.ConfigurationSettings.GetConfig("ApplicationConfiguration");
    System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");
    System.Configuration.ConfigurationSettings.GetConfig("SourceViewer");      
}
ConfigurationSettings 类还提供了一个公共方法 ConfigurationSettings.GetConfig() 用于返回用户定义的配置节的配置设置,传入的参数 section name ,如 "ApplicationConfiguration" ,表示要读取的配置节。
 
NameValueCollection nv=new NameValueCollection();
// 实例化 NameValueCollection 类对象
nv=(NameValueCollection)ConfigurationSettings.GetConfig("ApplicationConfiguration ");
// 返回用户定义的配置节的设置
return nv["SystemFramework.Tracing.Enabled"].ToString();
// 返回特定键值,如 SystemFramework.Tracing.Enabled
 
不过, ConfigurationSettings.GetConfig() 方法在调用时,自动调用 Create() 方法,可以看到 ApplicationConfiguration.Create() 方法正是用来读取指定 section 的配置,并初始化设置参数。
 
Global.asax Application_OnStart 事件处理程序向 SystemFramework ApplicationConfiguration OnApplicationStart 方法发出调用,正是上述的代码片断。
 
2 Code Snippet 2 Global.asax Application_OnStart() 方法
void Application_OnStart()
{
    ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath ));
    string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ),"remotingclient.cfg");
    if(File.Exists(configPath))
        RemotingConfiguration.Configure(configPath);
}
该方法肩负二大任务:( 1 )调用 ApplicationConfiguration.OnApplicationStart() 方法,并传入 application 的根目录( Root Directory )。( 2 )检测 Client 端的 remoting 配置文件是否存在(其实是 web server 端),如果存在,则读取并初始化 remoting 配置信息,如配置通道 Channel 等等,详见 Duwamish 部署方案篇
 
2 ,读取 web.config Duwamish 相关的一些配置- Common/DuwamishConfiguration.cs
Common/DuwamishConfiguration.cs 也实现 IconfigurationSectionHandler 接口,与 SystemFramework/ApplicationConfiguration.cs 类相似。
 
DuwamishConfiguration 配置节包括如下一些配置信息:
Database connection string Database 连接串) Duwamish.DataAccess.ConnectionString ,是否允许页面缓存 Duwamish.Web.EnablePageCache ,页面缓存过期时间 Duwamish.Web.PageCacheExpiresInSeconds ,是否允许 SSL 连接 Duwamish.Web.EnableSsl 等等。
 
如上所述,调用 DuwamishConfiguration Class 是由 SystemFramework/ApplicationConfiguration.cs OnApplicationStart() 方法完成的:
System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");
 
看看页面缓存配置在 web page 中如何使用的( web/book.aspx.cs 文件为例):
//
// If everything succeeded, then enable page caching as indicated
// by the current application configuration.
//
if ( DuwamishConfiguration.EnablePageCache )
{
    //Enable Page Caching...
    Response.Cache.SetExpires ( DateTime.Now.AddSeconds(DuwamishConfiguration.PageCacheExpiresInSeconds));
    Response.Cache.SetCacheability(HttpCacheability.Public);
}
Page_Load 事件中最后判断是否允许页面缓存。
 
3 ,验证数据合法性类- SystemFramework/ApplicationAssert.cs
SystemFramework/ApplicationAssert.cs Class 用来进行错误检测,并调用 SystemFramework/ApplicationLog.cs Class 记录错误日志。
 
学习其中的部分代码片断:
1 Code Snippet 1 – Check Method
[ConditionalAttribute("DEBUG")]
public static void Check(bool condition, String errorText, int lineNumber)
{
    if ( !condition )
    {
        String detailMessage = String.Empty;
        StringBuilder strBuilder;
        GenerateStackTrace(lineNumber, out detailMessage);
        strBuilder = new StringBuilder();
        strBuilder.Append("Assert: ").Append("/r/n").Append(errorText).Append("/r/n").Append(detailMessage);
        ApplicationLog.WriteWarning(strBuilder.ToString());
        System.Diagnostics.Debug.Fail(errorText, detailMessage);
    }
}
 
[ConditionalAttribute("DEBUG")] 定义 Check() 方法为 conditional method ,如果预处理符号( preprocessor symbol )没有定义, compiler 不仅忽略该方法,而且忽略对该方法的调用,和 #if DEBUG / #else / #endif 有些类似。
 
该方法用来判断条件 condition 是否为 true ,如果为 false ,则调用 SystemFramework/ApplicationLog.WriteWarning() 方法记录错误日志。
 
2 Code Snippet 2 – CheckCondition Method
public static void CheckCondition(bool condition, String errorText, int lineNumber)
{
    //Test the condition
    if ( !condition )
    {
        //Assert and throw if the condition is not met
        String detailMessage;
        GenerateStackTrace(lineNumber, out detailMessage);
        Debug.Fail(errorText, detailMessage);
 
        throw new ApplicationException(errorText);
    }
}
 
该方法一般用来在进行前置条件判断,如 condition false ,则抛出 exception
 
4 log 日志类- SystemFramework/ApplicationLog.cs
ApplicationLog 类实现 Duwamish 7.0 中的记录和跟踪。 Web.Config 文件中的配置设置确定是输出到 EventLog 文件、跟踪日志文件还是两者。下面是 Web.Config 文件中的 <ApplicationConfiguration> 节,它指定 EventLog 设置:
 
<ApplicationConfiguration>
    <!-- Event log settings -->
    <add key="SystemFramework.EventLog.Enabled" value="True" />
    <add key="SystemFramework.EventLog.Machine" value="." />
    <add key="SystemFramework.EventLog.SourceName" value="Duwamish7" />
   
    <add key="SystemFramework.EventLog.LogLevel" value="1" />
    <!-- Use the standard TraceLevel values:
             0 = Off
             1 = Error
             2 = Warning
             3 = Info
             4 = Verbose -->
Web.Config 文件的同一节还指定跟踪配置。 Duwamish 7.0 跟踪日志的默认位置是: [ 安装 Visual Studio .NET 的驱动器号 ]:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Web/DuwamishTrace.txt
 
在实际的应用系统开发中,用来提供 Log 功能的类应该比这个 ApplicationLog 类要好,这样就不去分析了,如 Microsoft Exception Management Application Block 就不错。
 
5 Web.config 配置文件-使用 Web.congfig 文件存储 application 设置
Duwamish 7.0 通过使用 Forms 身份验证来实现安全性。 Forms 身份验证将未经授权的用户重定向到 Web 窗体,该窗体提示用户输入其电子邮件地址和密码。
 
1 )配置 Forms 身份验证
Web.config 文件中的设置配置 Forms 身份验证。对于 Duwamish 7.0 Web.Config 文件按如下所述指定 Forms 身份验证的使用:
    <authentication mode="Forms">
      <forms name=".ADUAUTH" loginUrl="secure/logon.aspx" protection="All">
      </forms>
    </authentication>
    <authorization>
      <allow users="*" />
    </authorization>
 
authentication 元素只能在计算机、站点或应用程序级别声明。如果试图在配置文件中的子目录或页级别上进行声明,则将产生分析器错误信息。
 
如上所示, Web.Config .ADUAUTH 指定为身份验证 Cookie 的名称。当用户请求受限资源时,公共语言运行库将未经授权的用户重定向到在上面的 Web.Config 设置中指定的 Login.aspx protection="All" 设置指定应用程序使用数据验证和加密来保护 Cookie 。若要进一步限制资源, Duwamish 7.0 会将安全资源放置到名为 secure 的子文件夹中并使用额外的 Web.Config 文件(在 secure 文件夹),在该文件中指定只有经过身份验证的用户才可访问该子文件夹的内容。
    <authorization>
      <deny users="?" />
      <allow users="*" />
    </authorization>
 
<deny users="?" /> 标记指定拒绝对所有匿名用户的访问。 <allow users="*" /> 标签允许访问所有已验证身份的用户。
 
经过 secure/logon.aspx 认证通过的请求,重新定向最初的 URL
// 将已验证身份的用户重定向回最初请求的 URL
FormsAuthentication.RedirectFromLoginPage("*", false);
 
2 )用户定义的配置节
 <configSections>
    <section name="ApplicationConfiguration" type="Duwamish7.SystemFramework.ApplicationConfiguration, Duwamish7.SystemFramework" />
    <section name="DuwamishConfiguration" type="Duwamish7.Common.DuwamishConfiguration, Duwamish7.Common" />
    <section name="SourceViewer" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
 </configSections>
 
section 元素包含配置节声明, name 指定配置节的名称, type 指定从配置文件中读取节的配置节处理程序类的名称。配置节处理程序 ( 即实现 IConfigurationSectionHandler 接口的类 ) 读取设置。
 
上面定义了 3 个配置节处理程序: Duwamish7.SystemFramework.ApplicationConfiguration
Duwamish7.Common.DuwamishConfiguration
System.Configuration.NameValueSectionHandler
 
前面两个配置节处理程序是由 application 提供的,后面的 System.Configuration.NameValueSectionHandler .Net Framework 提供的,该类也实现了 IconfigurationSectionHandler 接口,用来提供配置节中的名称 / 值对配置信息。
 
 
6 web/PageBase.cs 基类
最后谈谈 web/PageBase.cs 基类吧,所有 Duwamish 7.0 中的所有 web 页都继承名为 PageBase 的基类,该类实现 Duwamish 7.0 应用程序的 ASP.Net 页中使用的常见属性和方法,这种设计方法值得推荐。
 
Code Snippet 分析:
/// <summary>
///     Handles errors that may be encountered when displaying this page.
///     <param name="e">An EventArgs that contains the event data.</param>
/// </summary>
protected override void OnError(EventArgs e)
{
    ApplicationLog.WriteError(ApplicationLog.FormatException(Server.GetLastError(), UNHANDLED_EXCEPTION));
    base.OnError(e);
}
重载 OnError 方法,使 application 遭遇到未处理错误的时候,自动调用 ApplicationLog.WriteError() 来记录错误日志。
 
另外,觉得 Duwamish password 处理有些特别,是以 byte 形式存放在 Database 中,避免明文的方式,以提高安全性(将在《 Duwamish 密码分析篇 》进行中分析)。
 
 
Reference:
1, MSDN, Duwamish 7.0
Duwamish 密码分析篇 , Part 1
 
Written by: Rickie Lee
Nov. 05, 2004
 
继续前面关于 Duwamish POST ,这里将学习 Duwamish 中关于 Password 的处理方式。 Duwamish 7.0 范例中的帐户密码通过 SHA1 散列运算和对散列执行 Salt 运算后, 是以 byte 形式存放在 Database 中,避免明文的方式 ,以提高系统的安全性。
 
Duwamish 的用户注册部分是封装在 /web/modules/accountmodule.ascx 用户控件内。随便提一下, Duwamish web tier 中采用了大量的 user control ,并且所有的 user control 都继承 /web/ModuleBase.cs 类,与 web page 继承 PageBase.cs 类相似,这种做法值得推荐。 Duwamish user control 主要是封装一些相应的功能,模块化。这样不仅可以在本 web 项目内重用,而且以后维护也比较方便,如 /web/modules/accountmodule.ascx user control 就封装了用户注册部分的功能。
 
下面看看【用户注册】功能模块具体的实现代码( /web/modules/accountmodule.ascx ):
1 获取用户登记 / 注册 password ,并帐户密码执行散列 运算。
byte [] bytePassword = null;
String tmpPassword = PasswordTextBox.Text;
 
if (tmpPassword == ConfirmPasswordTextBox.Text)
{
    SHA1 sha1 = SHA1.Create();
    bytePassword = sha1.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));
}
……
retVal = (new CustomerSystem()).CreateCustomer(EmailTextBox.Text,
                                           bytePassword,
                                           AcctNameTextBox.Text,
                                           AddressTextBox.Text,
                                           CountryTextBox.Text,
                                           PhoneTextBox.Text,
                                           FaxTextBox.Text,
                                           out moduleCustomerInfo);
 
先使用实现 160 SHA-1 标准的 System.Security.Cryptography 命名空间对密码进行散列运算。然后调用 BusinessFacade/CustomerSystem 类的 CreateCustomer() 方法。
 
知识点:
散列简介
散列( Hash )是一种单向算法,一旦数据被转换,将无法再获得其原始值。大多数开发人员使用数据库存储密码,如果密码直接以明文的形式存放在数据库中,则开发人员也能够看到这些密码,甚至包括用户的 Credit Card 信息。
不过,我们可以使用散列算法对密码进行加密,然后再将其存储在数据库中。用户输入密码后,可以再次使用散列算法对其进行转换,然后将其与存储在数据库中的散列进行比较。散列的特点之一是,即使原始数据只发生一个小小的改动,数据的散列也会发生非常大的变化。 Rickie Ricky 这两个单词非常相似,但使用散列算法加密后的结果却相去甚远。你可能根本看不出二者之间有什么相似之处。
 
.NET 开发人员可以使用多种散列算法类。最常用的是 SHA1 MD5 。下面我们看一下如何为 Rickie 这样的普通字符串生成散列,使任何人都无法识别它。
1 )使用 SHA1 生成散列
通过如下的示例代码,来演示如何通过 SHA1 生成散列:
byte [] bytePassword = null;
string tmpPassword = txtPassword.Text.Trim();
 
// 创建新的加密服务提供程序对象
SHA1 sha1 = SHA1.Create();
// 将原始字符串转换成字节数组,然后计算散列,并返回一个字节数组
bytePassword = sha1.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));
// Releases all resources used by the System.Security.Cryptography.HashAlgorithm.
sha1.Clear();
// 返回散列值的 Base64 编码字符串
txtResults.Text = Convert.ToBase64String(bytePassword);
 
传递不同的字符串值来调用该例程,查看散列值的变化。例如,如果将字符串 Rickie 传递给该例程,输出结果:
v8ocXHBvlh4EqY/2HsJNH5XBVG0=
现在,将此过程中的输入值更改为 Ricky 。你将看到以下输出结果:
luQsSa61sB/7PT9piDx+OAGqCnI=
 
如此可见,输入字符串的一个小小变化就会产生完全不同的字符组合。这正是散列算法之所以有效的原因,它使我们很难找到输入字符串的规律,也很难根据加密后的字符弄清楚字符串原来的模样。
 
2 )使用 MD5 也可以生成散列
通过如下的示例代码,来演示如何通过 MD5 生成散列:
byte [] bytePassword = null;
string tmpPassword = txtPassword.Text.Trim();
 
MD5 md5 = MD5.Create();
bytePassword = md5.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));
// Releases all resources used by the System.Security.Cryptography.HashAlgorithm.
md5.Clear();
txtResults.Text = Convert.ToBase64String(bytePassword);
 
输入 Rickie MD5 散列算法的输出结果:
YUqR1JfNxrciyG0ixNj58A==
 
同样,加密后的字符串看起来也与原始输入相去甚远。这些散列算法对于创建没有任何意义的密码来说非常有用,也使黑客很难猜出这些密码。之所以使用散列算法,是因为可以用这种算法对密码进行加密并将其存储在数据库中。然后,当用户输入真实密码时,需要先对用户输入的密码进行同样的散列,然后通过网络发送到数据库中,比较它与数据库中的密码是否匹配。
 
请记住,散列是单向操作。使用散列算法对原始密码加密后将无法再恢复。
 
上述两种散列算法都执行同一种操作。不同之处只在于生成散列的密钥大小以及使用的算法。使用的密钥越大,加密就越安全。例如, MD5 使用的加密密钥比 SHA1 使用的密钥大,因此 MD5 散列较难破解。
 
对于散列算法要考虑的另外一点是,从实践或理论的角度上看是否存在冲突的可能性。冲突是我们所不希望的,因为两个不同的单词可能会生成相同的散列。例如, SHA1 从实践或理论上来讲没有发生冲突的可能性。 MD5 从理论上讲有发生冲突的可能性,但从实践上讲没有发生冲突的可能性。因此,选择哪种算法归根结底取决于所需要的安全级别。
 
3 Summary
一般情况下,将上述加密的字节数组,通过使用 Convert.ToBase64String(bytePassword) 方法把字节数组转换成 Base64 编码的字符串,然后存储在数据库中即可完成一般的商业应用。
 
 
2 ,调用 BusinessFacade/CustomerSystem 类,对散列执行 Salt 运算。
到目前为止,散列算法暴露出来的问题之一是,如果两个用户碰巧使用相同的密码,那么散列值将完全相同。如果黑客看到您存储密码的表格,会从中找到规律并明白您很可能使用了常见的词语,然后黑客会开始词典攻击以确定这些密码。要确保任何两个用户密码的散列值都不相同,一种方法是在加密密码之前,在每个用户的密码中添加一个唯一的值。这个唯一值称为“盐”值( Salt )。
 
虽然对密码执行散列运算是一个好的开端,但若要增加免受潜在攻击的安全性,则可以对密码散列执行 Salt 运算。 Salt 就是在已执行散列运算的密码中插入的一个随机数字。这一策略有助于阻止潜在的攻击者利用预先计算的字典攻击。字典攻击是攻击者使用密钥的所有可能组合来破解密码的攻击。当您使用 Salt 值使散列运算进一步随机化后,攻击者将需要为每个 Salt 值创建一个字典,这将使攻击变得非常复杂且成本极高。
 
Salt 值随散列存储在一起,并且未经过加密。所存储的 Salt 值可以在随后用于密码验证。
 
下面看看 Duwamish 7.0 中是如何实现 Salt 运算:
1 BusinessFacade/CustomerSystem class Create Customer() 方法
public bool CreateCustomer(String emailAddress,
                           byte [] password,
                           String name,
                           String address,
                           String country,
                           String phoneNumber,
                           String fax,
                           out CustomerData custData)
{
    // create a salted password
    byte [] saltedPassword = CreateDbPassword(password);
 
    //
    // Create a new row
    //
    custData = new CustomerData();
   
    DataTable table = custData.Tables[CustomerData.CUSTOMERS_TABLE];
    DataRow row = table.NewRow();
    //
    // Fill input data into new row
    //
    row[CustomerData.EMAIL_FIELD] = emailAddress;
    row[CustomerData.PASSWORD_FIELD] = saltedPassword;
    row[CustomerData.NAME_FIELD] = name;
    row[CustomerData.ADDRESS_FIELD] = address;
    row[CustomerData.COUNTRY_FIELD] = country;
    row[CustomerData.PHONE_FIELD] = phoneNumber;
    row[CustomerData.FAX_FIELD] = fax;
    //
    // Add it to the table
    //
    table.Rows.Add(row);
    // 调用 Business rules tier Customer Class
    // Insert the customer using the business rules
    //
    return (new Customer()).Insert(custData);
}
首先调用 Facade/CustomerSystem 类的私有方法 CreateDbPassword() ,获取对散列执行 Salt 运算结果(长度为 24 个字节的 byte 数组),然后调用 Business rules tier 中的 Customer class Insert() 方法,将用户信息,包括密码存放在数据库中。
 
2 Facade/CustomerSystem 类的私有方法 CreateDbPassword()
// create salted password to save in Db
private byte [] CreateDbPassword(byte[] unsaltedPassword)
{
          //Create a salt value
          byte[] saltValue = new byte[saltLength];
          RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
          // 用加密型强随机字节填充的数组
          rng.GetBytes(saltValue);
         
          return CreateSaltedPassword(saltValue, unsaltedPassword);
}
上述代码片断使用 .NET Framework RNGCryptoServiceProvider 创建一个随机的数字字符串。 RNG 表示随机数生成器。该类可以创建一个任意长度的随机字节数组,长度由您指定。您可以使用此随机字节数组作为散列算法的 Salt 值。要采用这种方法,必须安全地存储该 Salt 值。
 
saltLength=4 (常量), Duwamish 7 示例用 RNGCryptoServiceProvider 创建一个 4 字节 Salt 值。然后调用 Facade/CustomerSystem 类的私有方法 CreateSaltedPassword() ,获取对散列执行 Salt 运算后的结果。
 
3 Facade/CustomerSystem 类的私有方法 CreateSaltedPassword()
// create a salted password given the salt value
private byte[] CreateSaltedPassword(byte[] saltValue, byte[] unsaltedPassword)
{
          // add the salt to the hash
          byte[] rawSalted = new byte[unsaltedPassword.Length + saltValue.Length];
         
          // Copies all the elements of the current one-dimensional System.Array to the specified one-dimensional System.Array starting at the specified destination System.Array index.
         
          unsaltedPassword.CopyTo(rawSalted,0);
          saltValue.CopyTo(rawSalted,unsaltedPassword.Length);
         
          //Create the salted hash                      
          SHA1 sha1 = SHA1.Create();
          byte[] saltedPassword = sha1.ComputeHash(rawSalted);
 
          // add the salt value to the salted hash
          byte[] dbPassword = new byte[saltedPassword.Length + saltValue.Length];
          saltedPassword.CopyTo(dbPassword,0);
          saltValue.CopyTo(dbPassword,saltedPassword.Length);
 
          return dbPassword;
}
 
该方法根据传入的 Salt 值(长度为 4 个字节的 byte 数组)和已执行散列运算的密码(长度为 20 个字节的 byte 数组),拼接为长度为 24 byte 数组。然后对上述拼接后的数组再进行 SHA1 散列运算,得到结果 saltedPassword (长度为 20 个字节的 byte 数组)。
 
最后将 saltedPassword (长度为 20 个字节的 byte 数组)和 Salt 值(长度为 4 个字节的 byte 数组)拼接为 dbPassword (长度为 4 个字节的 byte 数组)返回。
 
3 ,调用 BusinessRules/Customer 类的 Insert() 方法。
Insert() 方法根据传入的 CustomerData 对象,验证数据的合法性,然后调用 Data Access tier Customers 对象的 InsertCustomer() 方法。
具体代码请参考 Duwamish 7.0 范例。
 
4 ,调用 DataAccess/Customers 类的 InsertCustomer() 方法。
InsertCustomer() 方法根据传入的 CustomerData 对象,调用 Database 端的 Stored Procedure ,执行真正的数据库 insert 操作。可以观察到 Duwamish7 Database Customers 表的 Password 字段类型为 binary 且长度为 24
具体代码请参考 Duwamish 7.0 范例。
 
下一篇 POST Duwamish 密码分析篇 Part 2 将分析【用户登录】流程的密码验证过程。
 
 
References:
1, MSDN, Duwamish 7.0
2, Paul D. Sheriff, Microsoft .NET 中的简化加密 , http://www.microsoft.com/china/MSDN/library/archives/library/dnnetsec/html/cryptosimplified.asp
Duwamish 密码分析篇 , Part 2
 
 
继续前面关于 Duwamish POST ,这里将学习 Duwamish 中关于 Password 的处理方式。 Duwamish 7.0 范例中的帐户密码通过 SHA1 散列运算和对散列执行 Salt 运算后,是以 byte 形式存放在 Database 中,避免明文的方式,以提高系统的安全性。
 
1 ,【用户登录】过程概述
Web 层中启动登录过程。用户输入电子邮件地址和密码(凭据),然后单击“ Logon ”(登录)按钮,这将调用 Duwamish7.Web.Logon.LogonButton_Click 方法。下一步, Duwamish7.Web.Logon.LogonButton_Click 方法创建密码的散列表示形式,并将凭据传递给业务外观层的 Duwamish7.BusinessFacade.CustomerSystem.GetCustomerByEmail 方法。接着 Duwamish7.DataAccess.Customers.LoadCustomerByEmail 方法调用数据访问层,后者又调用 GetCustomerByEmail 存储过程 (SPROC) 。然后通过 ComparePasswords 方法,相对于从数据库中检索的经过 salt 和散列运算的密码来验证散列密码。如果凭据有效,则客户帐户信息成功地存储到 Cart 对象,并且 ASP.NET Forms 身份验证通过 pageBase ShoppingCart.Customer() 属性验证凭据。如果凭据无效,则 MismatchLabel 设置为可见,它在 ASP.NET 页上显示下面的内容:“ Invalid email address or password- please try again ”(电子邮件地址或密码无效,请再试一次)。
 
2 ,下面看看【用户登录】验证功能模块具体的实现代码
1 Duwamish7.Web.Logon.LogonButton_Click 方法
该方法首先创建用户输入密码的 SHA1 散列形式,然后调用业务外观层的 Duwamish7.BusinessFacade.CustomerSystem.GetCustomerByEmail 方法。
    //
    // Check the Email and Password combination
    //
    SHA1 sha1 = SHA1.Create();
    byte [] password = sha1.ComputeHash(Encoding.Unicode.GetBytes(LogonPasswordTextBox.Text));
 
    custData = (new CustomerSystem()).GetCustomerByEmail(LogonEmailTextBox.Text, password);
   
    if (custData != null)   //were they valid?
    {
        //
        // 1. Update customer in session.
        // 2. Update customer in cart.
        //
        base.Customer = custData;
        base.ShoppingCart().Customer = custData;
        // 将已验证身份的用户重定向回最初请求的 URL
        FormsAuthentication.RedirectFromLoginPage("*", false);
    }
    else
    {
        MismatchLabel.Visible = true;
    }
 
如果凭据有效,则客户帐户信息成功地存储到 Cart 对象,并且 ASP.NET Forms 身份验证通过 pageBase ShoppingCart.Customer() 属性验证凭据。如果凭据无效,则 MismatchLabel 设置为可见,它在 ASP.NET 页上显示下面的内容:“ Invalid email address or password- please try again ”(电子邮件地址或密码无效,请再试一次)
 
2 )业务外观层的 CustomerSystem.GetCustomerByEmail 方法
根据用户的 email ,获取 Database Customer Password ,该 Password 已执行散列运算和对散列执行过 Salt 运算,是 24 个字节长度的 byte 数组。
public CustomerData GetCustomerByEmail(String emailAddress, byte [] password)
{
      //
      // Check preconditions
      //
      ApplicationAssert.CheckCondition(emailAddress != String.Empty, "Email address is required", ApplicationAssert.LineNumber);
      ApplicationAssert.CheckCondition(password.Length != 0, "Password is required", ApplicationAssert.LineNumber);
      //
      // Get the customer dataSet
      //
      CustomerData dataSet;
      using (DataAccess.Customers customersDataAccess = new DataAccess.Customers())
      {
           dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress);
      }
      //   
      // Verify the customer's password
      //
      DataRowCollection rows = dataSet.Tables[CustomerData.CUSTOMERS_TABLE].Rows;
 
      if ( ( rows.Count == 1 ))
      {
           byte [] dbPassword = (byte[])rows[0][CustomerData.PASSWORD_FIELD];
 
           if (ComparePasswords (dbPassword, password))
                 return dataSet;
           else
                 return null;
      }
      else
           return null;
}
 
在获取到 DataAccess 层返回的 CustomerData 对象后,进一步调用类中的私有方法 ComparePasswords ()。该方法负责从 Password 字段值中提取 Salt 值,然后运用该 Salt 值对传入的 SHA1 散列执行 Salt 运算。
 
其中 CreateSaltedPassword() 方法和前面【用户注册】过程相同,用来对散列结果再次执行 Salt 运算。
// compare the hashed password against the stored password
private bool ComparePasswords(byte[] storedPassword, byte[] hashedPassword)
{
      if (storedPassword == null || hashedPassword == null || hashedPassword.Length != storedPassword.Length - saltLength)
           return false;
 
      // get the saved saltValue
      // 获取已保存在 password 数据字段中的 Salt
      byte[] saltValue = new byte[saltLength];
      int saltOffset = storedPassword.Length - saltLength;
      for (int i = 0; i < saltLength; i++)
           saltValue[i] = storedPassword[saltOffset + i];
 
      byte[] saltedPassword = CreateSaltedPassword(saltValue, hashedPassword);
 
      // compare the values
      return CompareByteArray(storedPassword, saltedPassword);
}
 
按字节比较两个字节数组是否相等,分别传入数据表中 Password 字段 byte 数组和对当前散列执行 Salt 运算后的 byte 数组。
// compare the contents of two byte arrays
private bool CompareByteArray(byte[] array1, byte[] array2)
{
      if (array1.Length != array2.Length)
           return false;
      for (int i = 0; i < array1.Length; i++)
      {
           if (array1[i] != array2[i])
                 return false;
      }
      return true;
}
 
3 )数据访问层的 Customers.LoadCustomerByEmail() 方法
该方法根据传入的 emailAddress 参数,查询 Database ,返回 CustomerData 对象。过程比较简单,详细代码请查询 Duwamish 7.0 范例。
 
3 Summary
通过对 Duwamish 7.0 范例中用户注册和用户登录验证过程的分析,我们确信用户 Password 以安全的 Salt 运算结果存放在后台的 Database 中。
其实,在实际的应用系统中,上述的加密过程有一个小问题:就是当有大量的用户忘记了自己的 Password ,如何帮助他们恢复自己的 Password 呢?这个问题地球人都不知道,只有通过另外的 application 来将这些 Password 重新 Update 为新的 Password ,因为散列是单向操作,使用散列算法对原始密码加密后将无法再恢复。
 
因此,将在 Duwamish 密码分析篇 , Part 3 中分析如何实现双向的加密 / 解密操作,来克服上面提出的问题。
 
References:
1, MSDN, Duwamish 7.0
Duwamish 密码分析篇 , Part 3
 
Written by: Rickie Lee
Nov. 07, 2004
 
通过前面关于《 Duwamish 密码分析篇 , Part 1 2 》的 POST ,可以了解到 Duwamish 中关于 Password 的处理方式。 Duwamish 7.0 范例中的帐户密码通过 SHA1 散列运算和对散列执行 Salt 运算后,然后以 byte 形式存放在 Database 中,避免明文的方式,以提高系统的安全性。
 
但是,由于散列是单向操作,使用散列算法对原始密码加密后将无法再恢复。因此,在实际的应用系统中,上述的加密过程有一个小问题:就是当有大量的用户忘记了自己的 Password ,如何帮助他们恢复自己的 Password 呢?这个问题地球人都不知道。
 
因此,对于一般的商业应用,通过 .Net 的加密 / 解密类库来同时实现上述目的,既加密 Password 等重要信息,同时也可以在必要的时候解密这些信息。
 
加密算法使可以将数据掩盖起来,除了特定人员能够对其解密外,其他人员不大可能通过数学方法读取该数据。但如果希望读取该数据,则可以为其提供一个特定的“密钥”,使其能够解密并读取数据。 .NET Framework 中有多种可用的加密 / 解密算法。
 
目前有两种加密方法:
对称算法(或密钥算法) 的速度非常快,非常适于加密大型的数据流。这些算法可以加密数据,也可以解密数据。对称加密技术的数据交换两边(即加密方和解密方)必须使用一个保密的私有密钥。
不对称算法(或公钥算法) 没有对称算法快,但其代码较难破密。这些算法取决于两个密钥,一个是私钥,另一个是公钥。公钥用来加密消息,私钥是可以解密该消息的唯一密钥。公钥和私钥通过数学方法链接在一起,因此要成功进行加密交换,必须获得这两个密钥。由于可能会影响到计算机性能,因此不对称算法不太适用于加密大量数据。不对称算法的常见用法是将对称密钥和初始化向量加密并传输给对方。然后在双方之间来回发送的消息中使用对称算法加密和解密数据。不对称算法主要有 RSA DSA 等,主要用于网络数据的加密。保护 HTTP 传输安全的 SSL 就是使用非对称技术。
 
本文先主要学习 .Net 中如下对称算法(或密钥算法)类库,包括以下几种:
DES Data Encryption Standard ), TripleDES RC2 Rijndael
对称算法(或密钥算法)使用一个密钥和一个初始化向量 (Initialization Vector IV) 来保证数据的安全。加密的功效取决于所用密钥的大小,密钥越长,保密性越强。典型的密钥长度有 64 位、 128 位、 192 位、 256 位和 512 位。使用该数据的双方都必须知道这个密钥和初始化向量才能够加密和解密数据。必须确保该密钥的安全,否则其他人将有可能解密该数据并读取该消息。初始化向量只是一个随机生成的字符集,使用它可以确保任何两个文本都不会生成相同的加密数据。然后,在此基础上学习开发一套标准的加密 / 解密通用类库,供今后开发应用系统时使用。
 
.Net Framework 内置加密 / 解密算法类库
.NET Framework 为各种最广泛使用的对称加密算法提供了支持。 .NET 构架从基本的 SymmetricAlgorithm 类扩展出来四种算法:
· System.Security.Cryptography.DES
· System.Security.Cryptography.TripleDES
· System.Security.Cryptography.RC2
· System.Security.Cryptography.Rijndael
.NET 的对称加密和解密通过 CryptoStream 类来处理的,它继承自 System.IO.Stream ,使用 CryptoStream 对象的 Write 方法将数据写入到内存数据流( Memory Stream ),这就是进行实际加密 / 解密的方法。
 
先以 DES 算法为例,了解 .Net 为我们提供的简单加密 / 解密过程:
1 DES 加密
上图 Encrypt 实例代码:
private void btnEncrypt_Click(object sender, System.EventArgs e)
{
          SymmetricAlgorithm mCSP = new DESCryptoServiceProvider();
          ICryptoTransform ct;
          MemoryStream ms;
          CryptoStream cs;
          byte[] byt;
          byte [] key, iv;
 
          // If this property is a null reference (Nothing in Visual Basic) when it is used,
          // GenerateKey is called to create a new random value.
          key = mCSP.Key;
          // If this property is a null reference (Nothing in Visual Basic) when it is used,
          // GenerateIV is called to create a new random value.
          iv = mCSP.IV;
          // Creates a symmetric Data Encryption Standard (DES) encryptor object.
          ct = mCSP.CreateEncryptor(key, iv);
          // Refresh textboxes
          txtKey.Text = Convert.ToBase64String(key);
          txtIV.Text = Convert.ToBase64String(iv);
 
          byt = Encoding.Unicode.GetBytes(txtOriginal.Text.Trim());
 
          ms = new MemoryStream();
          cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
          cs.Write(byt, 0, byt.Length);
          cs.FlushFinalBlock();
 
          cs.Close();
 
          txtResults.Text = Convert.ToBase64String(ms.ToArray());
}
首先创建 DESCryptoServiceProvider 对象,要使用对称算法,必须提供要使用的密钥。每个 CryptoSymmetricAlgorithm 实现都提供一种 GenerateKey 方法。它们实际上使用的是公共语言运行时 (CLR) 类中内置的随机数生成器类。在首次调用 DESCryptoServiceProvider 对象的 Key 属性时,因为该属性为 Null ,则自动调用 GenerateKey 方法来生成随机 Key 。密钥的大小取决于用来加密的特定提供程序。例如, DES 密钥的大小为 64 位,而 TripleDES 密钥的大小为 192 位。每个 SymmetricAlgorithm 类上都有一个 KeySize 属性,它将返回用于生成密钥的密钥大小。
然后是需要生成初始化向量 (IV) ,如果 IV 属性为 Null ,也是自动调用 GenerateIV 方法,生成 IV 。只要使用的 IV 不同,即使密钥相同,同一个数据也会被加密成完全不同的值。当然,你可以自己指定特定的 Key IV ,但要注意的不同的加密算法,可能采用不同的 Key 长度和 IV 长度。
ICryptoTransform 是一个接口。需要此接口才能在任何服务提供程序上调用 CreateEncryptor 方法,服务提供程序将返回定义该接口的实际 encryptor 对象。
Encoding.Unicode.GetBytes() 方法负责将原始字符串转换成字节数组。大多数 .NET 加密算法处理的是字节数组而不是字符串。
现在可以执行实际的加密了。此进程需要创建一个数据流,用于将加密的字节写入到其中。要使用名为 ms MemoryStream 对象、 ICryptoTransform 对象(提供给 CryptoStream 类的构造函数)以及说明您希望在何种模式(读、写等)下创建该类的枚举常数。创建 CryptoStream 对象 cs 后,现在使用 CryptoStream 对象的 Write 方法将数据写入到内存数据流。这就是进行实际加密的方法,加密每个数据块时,数据将被写入 MemoryStream 对象。
创建 MemoryStream 后,该代码将在 CryptoStream 对象上执行 FlushFinalBlock 方法,以确保所有数据均被写入 MemoryStream 对象。并调用 CryptoStream 对象的 Close 方法关闭 CryptoStream 对象。
最后,调用 Convert.ToBase64String() 方法,该方法接受字节数组输入并使用 Base64 编码方法将加密结果编码为可读内容,输出到 TextBox 显示。
 
2 DES 解密
上图 Decrypt 实例代码:
private void btnDecrypt_Click(object sender, System.EventArgs e)
{
          SymmetricAlgorithm mCSP = new DESCryptoServiceProvider();
          ICryptoTransform ct;
          MemoryStream ms;
          CryptoStream cs;
          byte[] byt;
          // Sets the key for the symmetric algorithm.
          mCSP.Key = Convert.FromBase64String(txtKey.Text.Trim());
          // Sets the initialization vector (IV) for the symmetric algorithm.
          mCSP.IV = Convert.FromBase64String(txtIV.Text.Trim());
          // Creates a symmetric Data Encryption Standard (DES) decryptor object.
          ct = mCSP.CreateDecryptor(mCSP.Key, mCSP.IV);
 
          byt = Convert.FromBase64String(txtOriginal.Text.Trim());
 
          ms = new MemoryStream();
          cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
          cs.Write(byt, 0, byt.Length);
          cs.FlushFinalBlock();
 
          cs.Close();
 
          txtResults.Text = Encoding.Unicode.GetString(ms.ToArray());
 
}
 
Decrypt 过程代码与 Encrypt 过程很相似,只有细微的差别。首先, Decrypt 过程不需要重新生成随机的 Key IV ,必须使用与上述 Encrypt 过程中相同的 Key IV ,否则无法解密。
另外, ICryptoTransform 是一个接口,这里是在 DESCryptoServiceProvider 服务提供程序上调用 CreateDecryptor 方法,服务提供程序将返回定义该接口的实际 decryptor 对象。
 
.Net Framework 也支持其他加密 / 解密算法,如 TripleDES RC2 Rijndael ,并且只要简单替换上述代码的 DESCryptoServiceProvider 服务提供程序对象,就可以切换到对应的加密算法。正是基于这一点,我们可以非常方便地构建一套标准的加密 / 解密通用类库,请参考 Microsoft 的《 如何创建加密库 》,这里不重复了。
根据 Microsoft 的《 如何创建加密库 》编写的一个 DEMO
知识点: Base64 编码
前面的加密 / 解密过程中,使用到 .Net Framework 提供的关于 Base64 编码的方法:
1 Convert.ToBase64String() 方法,该方法接受字节数组输入并使用 Base64 编码方法将加密结果编码为可读内容
2 Convert.FromBase64String() 方法,该方法接受 Base64 编码的字符串,输出字节数组。
 
Base64 MIME 邮件中常用的编码方式之一。它的主要思想是将输入的字符串或数据编码成只含有 {'A'-'Z', 'a'-'z', '0'-'9', '+', '/'} 64 个可打印字符的串,故称为“ Base64 ”。
Base64 编码的方法是,将输入数据流每次取 6 bit ,不足 6bit 的补 0 ,用此 6 bit 的值 (0-63) 作为索引去查表,输出相应字符。这样,每 3 个字节将编码为 4 个字符 (3 × 8 4 × 6) (之后在 6 位的前面补两个 0 ,形成 8 位一个字节的形式),不满 4 个字符的以 '=' 填充。转换后的字符串理论上将要比原来的长 1/3
 
现在回到前面的 DES 算法中 Key 或者 IV Key 长度为 64 位( 8 个字节),为了让 Base64 编码是 4 的倍数,就要补 1 个等号。
 
 
References:
1, MSDN, Duwamish 7.0
Duwamish Web Services 分析篇
 
Written by: Rickie Lee
Nov. 08, 2004
 
Duwamish 7.0 web 项目中提供了一个 Web Service service/catalogservice.asmx ),以向 Internet 公开它的书目录搜索功能。 CatalogService Web Service 由一个 asmx 文件和一个代码隐藏文件组成,其中 ASMX 文件充当调用 Web Services 的客户端的基 URL ,代码隐藏文件包含实现 Web 服务的代码。不过,在整个 Duwamish 项目中并没有调用该 web service ,正如以前的 POST 中所提及的:
If you need to communicate between applications (even .NET apps) then use web services. Note this is not between tiers, but between applications – as in SOA (Service-Oriented Architecture). SOA is not useful INSIDE applications. It is only useful BETWEEN applications.
 
1. Web Services 概述
Web Services 既可以在内部由单个应用程序使用,也可通过 Internet 公开以供外部的应用程序使用。由于可以通过标准接口访问,因此 Web Services 使异类系统能够作为单个计算网络资源协同运行。
Web Services 并不追求一般的代码可移植性功能,而是为实现数据和系统的互操作性提供了一种可行的解决方案。 Web Services 使用基于 XML 的消息处理作为基本的数据通讯方式,以帮助消除使用不同组件模型、操作系统和编程语言的系统之间存在的差异。开发人员可以用像过去在创建分布式应用程序时使用组件一样的方式创建将来自各种平台的 Web Services 组合在一起的应用程序。
Web Services 的核心特征之一是服务的实现与使用之间的高度抽象化。通过将基于 XML 的消息处理机制, Web Services 客户端和 Web Services 提供程序之间除输入、输出和位置之外无需互相了解其他信息。
Web Services 向外界发布出一个能够通过 Web 进行调用的、平台无关的 API 。也就是说,你能够在任何你喜欢的平台上,用编程的方法通过 Web 来调用这个应用程序,进行基于 Web 的分布式计算和处理。 Web Services 平台是一套标准,它定义了应用程序如何在 Web 上实现互操作性。 Web Services 平台采用 XML 来表示数据的基本格式,采用 W3C 制定的 XML Schema(XSD) 来作为其数据类型系统。
组成 Web Services 平台的三个核心的技术规范分别为 SOAP WSDL UDDI SOAP 规范定义了 SOAP 消息的格式,以及怎样通过 HTTP 协议来使用 SOAP ,来执行 Web Services 的调用。 WSDL Web Services 描述语言)用来描述 Web Services 。因为其基于 XML ,所以 WSDL 文档既是机器可阅读的,又是人可阅读的。 UDDI (统一描述,发现和集成协议)标准定义了 Web Services 的发布与发现的方法。
从技术的角度来看, Web Services 可以被认为是一种部署在 Web 上的对象( Web Object ),因此,具有对象技术所承诺的所有优点;同时, Web Services 的基石是以 XML 为主的、开放的 Web 规范技术,因此,具有比任何现有对象技术更好的开放性。
 
2. Duwamish 中的 CatalogService Web Service
(1)CatalogServer.asmx文件中仅包含一行代码:
<%@ WebService Language="c#" Codebehind="CatalogService.cs" Class="Duwamish7.Web.Service.CatalogService" %>
 
2 CatalogService.cs 代码隐藏文件包含实现 web service 的代码:
CatalogService Web 服务实现 GetBooksByTopic GetBooksByTopicSecure Web 方法,返回值为 DataSet 类型(支持 XML 编码和序列化)。 Web Service 发布的上述 Web 方法均都有 WebMethodAttribute
 
WebMethodAttribute 向使用 ASP.NET 创建的 XML Web services 中的某个方法添加此特性后,就可以从远程 Web 客户端调用该方法。
 
另外还有一些辅助的 class 和方法(调用 BusinessFacade tier ),代码比较简单。
 
3 Web.config 配置文件 <webServices> 元素:可以配置使用 ASP.NET 创建的 XML Web services 的设置。
 
 
3. Summary
Web Services 不仅可用于异构平台的相互集成,也是分布式应用开发的一种技术。 Microsoft 在推 .Net Framework 时,尽心尽力吹捧这项技术,并冠以 XML Web Services 。不过由于 Web Services 的性能不好的问题,感觉目前在企业内部应用并不多,估计还不及 .Net Remoting 技术的应用。
Microsoft 还有一个 Web Services 的增强软件开发包: Web Services Enhancements (WSE) Version 2.0 ,主要提供如下特性:安全特性(数字签名和加密),消息路由,消息附件等等,从 Reference 1 可以下载。
现在, Web Services 方面的相关规范很多,如 WS-Security, WS-Policy, WS-Trust, WS-SecureConversation…… ,令人目不暇接,在不断地向前发展。从另外一个方面也表示, Web Services 技术目前在企业应用方面还不够成熟。
 
 
References:
2, MSDN, Duwamish 7.0
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值