构建自定义 IIS 7.0 服务器

作者:Mike Volodarsky

介绍

IIS 6.0 和早期版本在服务器本身实施大多数广泛使用的服务器功能。 相比之下,IIS 7.0 及更高版本的 Web 服务器引擎提供了模块化体系结构,在该体系结构之上,几乎所有服务器功能都作为可插入组件提供。 这可以实现全面的巨大改进,包括:

  • 能够准确控制服务器上加载/使用哪些功能集,删除不需要的功能以减少服务器的攻击面/内存占用情况
  • 能够用第三方或自定义实现替换每个功能
  • 能够根据服务器在服务器拓扑中的角色实现服务器专用化
  • 在细粒度和应用程序委派级别对服务器功能集进行高级控制

这些服务器组件(称为模块)在应用程序池工作进程初始化期间加载,并在服务器上提供请求处理服务。 每个 IIS 7.0 及更高版本的应用程序都是为该应用程序启用的模块所提供的服务以及这些服务所使用的关联内容的组合。 服务器提供模块扮演的两个主要角色:

  • 提供请求服务,例如身份验证或输出高速缓存(类似于 IIS 6.0 中的 ISAPI 筛选器)
  • 提供请求处理,例如静态文件处理、CGI 或 ASP.NET 页面处理(类似于 IIS 6.0 中的 ISAPI 扩展)

通过启用不同的模块,可以配置服务器以提供服务器上的应用程序所需的服务。

本文中演示的任务包括:

  • 检查服务器配置、默认配置以及默认加载到服务器上的模块集
  • 删除所有模块以将服务器精简到最低配置,并检查对内存占用情况的影响
  • 通过增量添加模块来构建自定义服务器,以支持特定场景

查看默认模块配置

主服务器配置包含在 applicationHost.config 文件中,该文件位于 IIS 配置目录 %windir%\system32\inetsrv\config\ 中。 我们查看了 <system.webServer>system.webServer 节组中包含的以下配置:

<globalModules> 节。 此服务器级别的节包含服务器工作进程加载的模块列表,以及实施其功能的关联本机 DLL。

<modules> 节。 此应用程序级别的节包含为特定应用程序启用的模块列表。 此节用于选择应在应用程序中处于活动状态的已加载模块的子集,并加载其他应用程序级模块。

<handlers> 节。 此 URL 级别的节包含服务器用来将传入请求映射到将处理该请求的特定模块的处理程序映射。 这与 IIS 6.0 脚本映射或 ASP.NET 类似,并提供到本机和托管内容类型处理程序的请求的统一映射。

所有 IIS 模块的完整描述可在 IIS 7.0 及更高级别的模块概述中找到。

创建配置备份

首先,备份服务器配置,以便在必要时还原它。 从以管理员身份运行的命令提示符中运行以下命令:

%windir%\system32\inetsrv\appcmd add backup initial

然后,可以通过运行以下命令将服务器配置还原到初始状态:

%windir%\system32\inetsrv\appcmd restore backup initial

检查模块的默认列表

导航到 <system.webServer>/<globalModules> 节。 此节只能在服务器级别配置,其中包含每个服务器工作进程加载的模块。 每个条目都配置一个具有特定名称的模块以及实施该模块功能的 DLL:

<globalModules>

    <!--several modules omitted -->

    <add name="BasicAuthenticationModule" image="…\authbas.dll" />

    <add name="WindowsAuthenticationModule" image="…\authsspi.dll" />

</globalModules>

查看默认服务器配置中各个模块的名称 - 我们看到熟悉的服务作为 IIS 6.0 中服务器的一部分提供:

Windows 身份验证模块,NTLM 请求身份验证

<add name="WindowsAuthenticationModule" image="…\authsspi.dll" />

静态文件处理程序模块,提供静态文件

<add name="StaticFileModule" image="…\static.dll" />

动态压缩模块,响应压缩

<add name="DynamicCompressionModule" image="…\compdyn.dll" />

导航到 <system.webServer>/<modules> 节。 此节可以在服务器或应用程序级别进行配置,指定为特定应用程序启用 <globalModules> 节中加载的哪些模块。 在大多数情况下,我们看到此节列出了我们在其中看到的模块的名称,默认情况下为所有应用程序启用它们。

注意

列表末尾有一些额外的项:这些是使用 ASP.NET 可扩展性模型开发的托管模块。 在使用 .NET 开发模块演练中了解有关构建托管模式的更多信息。

导航到 <system.webServer>/<handlers> 节。 此节可以在服务器、应用程序或 URL 级别进行配置,用于指定如何处理请求。 模块通常参与每个请求,而处理程序仅获取特定 URL 的请求。

压缩模块是模块的一个很好的例子。 压缩模块将查看每个响应并在需要时对其进行压缩。 ASP.NET 页处理程序是处理程序的良好示例。 它仅接收映射到它的请求,例如扩展名为 .aspx 的请求。 <handlers> 列表定义基于 URL 和动词的请求与用于处理该请求的处理模块之间的映射。 还有一些用于配置每个映射的额外信息,但这不是本主题的重点。

<handlers>
    <!-- certain details omitted -->
    <add name="CGI-exe" path="*.exe" verb="*" modules="CgiModule" ... />
    <add name="ISAPI-dll" path="*.dll" verb="*" modules="IsapiModule" ... />
    <add name="ASPClassic" path="*.asp" verb="GET,HEAD,POST"  modules="IsapiModule" ... />
</handlers>

检查服务器内存占用情况

  1. 打开 Internet Explorer,通过指定以下 URL 并按 Enter 向服务器发出请求:

    http://localhost/iisstart.htm
    

    这会启动服务器应用程序池,并提供 iisstart.htm 文档。

  2. 启动任务管理器,然后转到“进程”选项卡。由于 IIS 工作进程在不同的用户帐户下运行,因此必须选中“显示所有用户的进程”。 请注意 w3wp.exe 服务器工作进程的大小。
    显示 Windows 任务管理器的屏幕截图。已选择“进程”选项卡。
    图 1:显示 IIS 工作进程的任务管理器

  3. 现在执行以下命令行:

    TASKLIST /fi "imagename eq w3wp.exe" /m
    

    我们看到工作进程加载了 90 多个 DLL。 它们中的大多数位于 ...\intersrv\ 目录中 – 其中许多是我们在第一个任务中查看 <globalModules> 节时看到的模块 DLL,以及其他一些支持 .NET 框架和服务器运行时本身的模块 DLL。

精简服务器

在上一个任务中,我们检查了服务器加载的默认组件列表,其中包含超过 35 个模块,这些模块提供从身份验证到静态文件服务等各种服务。 服务器中加载的每个组件都会对服务器内存占用情况、攻击面、运行时性能以及启用的功能集产生影响。

在构建自己的自定义服务器(仅包含下一个任务所需的功能)之前,我们通过删除所有模块并运行空服务器来构建一个快速、小型且安全的 Web 服务器。

如果我们在上一个任务中更改了 applicationHost.config 文件,则可以通过从命令行运行 %windir%\system32\inetsrv\appcmd restore backup initial 将其还原到原始状态。

现在精简服务器。

  1. 使用文本编辑器打开 %windir%\system32\inetsrv\config\applicationHost.config

  2. 导航到 <system.webServer>/<globalModules> 节。

  3. 删除集合中的所有条目,以便仅保留空节定义:

    <globalModules> 
        <!—Remove Everything --> 
    </globalModules>
    
  4. 将项目粘贴到暂存记事本窗口中以供稍后使用。 对 <system.webServer>/<modules> 节重复相同的操作。 移除此节下的所有条目并将其粘贴到暂存记事本中以供以后使用。 这可以确保我们不会启用任何不再加载的模块。 将这些剪切的项目粘贴到暂存记事本窗口中以供以后使用。

  5. <system.webServer>/<handlers> 节重复相同的操作。 移除此节下的所有条目,以确保我们没有指定任何已禁用模块的处理程序映射。 将项目粘贴到暂存记事本中以供以后使用。 保存 applicationHost.config 文件以使更改生效。

检查已精简服务器的内存占用情况

此时,我们已准备好加载已精简的服务器 - 我们将重复前面的步骤来检查服务器的新内存占用情况。

  1. 打开 Internet Explorer,通过指定以下 URL 并按 Enter 向服务器发出请求:

    http://localhost/iisstart.htm
    

    这应该启动服务器应用程序池,并向浏览器返回错误,因为没有注册处理程序来提供所请求的资源。

  2. 运行任务管理器,然后转到“进程”选项卡。请注意 w3wp.exe 服务器工作进程的大小。

  3. 执行以下命令行:

    TASKLIST /fi "imagename eq w3wp.exe" /m
    

    观察到服务器的内存占用已减少到大约 8Mb。 在服务器时间范围内,空服务器的内存占用将进一步减少。

    仅加载 50 个 DLL(原先为 90 个或更多)- 这表示服务器未加载任何模块 DLL,后者直接或间接地导致了 DLL 计数差异。 不仅服务器上的服务被禁用,而且该过程中甚至没有加载这些功能的代码。 优化后,空服务器的 DLL 数量将明显降低。

在下一个任务中,我们将构建仅包含我们想要的功能的自定义服务器。

构建自定义服务器

在上一个任务中,我们将服务器精简到最低配置,仅运行核心服务器引擎,不加载任何其他模块。 现在,我们构建自定义服务器以用作公司网络上的 Web 文件服务器。 为此,我们使服务器仅提供以下服务:

  • 提供静态文件
  • 提供目录列表
  • 使用基本身份验证和基于 URL 的授权规则保护内容

支持服务器提供静态文件

若要执行此任务,假设我们已执行上一个任务并通过删除服务器正在运行的所有模块来精简服务器。 在此状态下,服务器始终向所有请求返回空的 401 错误响应,因为根本没有加载任何模块来提供任何类型的请求处理。

  1. 使用文本编辑器打开 %windir%\system32\inetsrv\config\applicationHost.config

  2. 导航到 <system.webServer>/<globalModules> 节。 在集合中添加下面以粗体显示的 2 行 - 从之前使用的暂存笔记本中复制它以保存默认集合项。 这会加载静态文件处理程序模块(负责服务静态文件请求)和匿名身份验证模块(为请求生成默认身份验证令牌):

    <globalModules>
        <add name="StaticFileModule" image="%windir%\System32\inetsrv\static.dll" />
        <add name="AnonymousAuthenticationModule" image="%windir%\System32\inetsrv\authanon.dll" />
    </globalModules>
    
  3. 导航到 <system.webServer>/<modules> 节。 通过添加下面以粗体显示的行来启用静态文件处理程序和匿名身份验证模式:

    <modules>
    
        <add name="AnonymousAuthenticationModule" />
    
        <add name="StaticFileModule" />
    
    </modules>
    
  4. 导航到 <system.webServer>/<handlers> 节。 通过添加下面以粗体显示的行,将静态文件处理程序映射到所有文件请求:

    <handlers>
        <add name="StaticFile" path="*" verb="GET,HEAD"  modules="StaticFileModule" resourceType="Either" requireAccess="Read"/>
    </handlers>
    
  5. 保存 applicationHost.config 文件。

  6. 打开 Internet Explorer,并向以下 URL 发出请求:

    http://localhost/iisstart.htm
    

    这将提供所请求的文档。 我们已经成功在服务器上启用了静态文件服务功能。

  7. 接下来,通过向以下 URL 发出请求来请求目录列表:

    http://localhost
    

    我们收到了一个空响应,因为由于当前没有加载、启用和映射到进程目录列表的处理程序 - 发送了一个空响应(200 正常)。 在下一个任务中,我们将添加处理程序。

启用服务器以提供目录列表

若要执行此任务,假定我们执行了以前的任务,将服务器精简为空,并添加了文件服务功能。

  1. 使用文本编辑器打开 %windir%\system32\inetsrv\config\applicationHost.config

  2. 和以前一样,添加以下配置以启用目录浏览模块,并将其映射到服务目录请求(此步骤之后的累积配置将与下面指定的完全一样,在上一步的基础上进行构建):

    <globalModules>
        <add name="AnonymousAuthenticationModule" image="%windir%\system32\inetsrv\authanon.dll" />
        <add name="StaticFileModule" image="%windir%\system32\inetsrv\static.dll" />
        <add name="DirectoryListingModule" image="%windir%\System32\inetsrv\dirlist.dll" />
    </globalModules>
    
    <modules>
        <add name="AnonymousAuthenticationModule" />
        <add name="StaticFileModule" />
        <add name="DirectoryListingModule" />
    </modules>
    
    <handlers>
        <add name="StaticFile" path="*" verb="GET,HEAD" modules="StaticFileModule,DirectoryListingModule"  resourceType="Either" requireAccess="Read" />
    </handlers>
    

    至此,我们已经在服务器上启用了目录列表功能。 但是,出于安全原因,该功能公开了额外的配置,用于控制是否允许目录列表。 此配置在 <system.webServer>/<directoryBrowse> 节中指定。

  3. 将条目更改为 <directoryBrowse enabled="true" />

  4. 保存 applicationHost.config 文件。

  5. 打开 Internet Explorer,并通过请求以下 URL 重复对目录的请求:

http://localhost

这将提供所请求目录的列表。 我们已成功在服务器上启用目录列表功能。

接下来,我们添加身份验证和授权服务,以保护服务器上的内容免受未经授权的访问。

通过 URL 授权保护资源

为了执行此任务,假设我们已执行前面的任务,将服务器精简为空,并添加文件服务和目录列表功能。

  1. 使用文本编辑器打开 %windir%\system32\inetsrv\config\applicationHost.config

  2. 这一次,我们添加两个模块:

    • 基本身份验证模块,支持针对服务器 Windows 凭据的 http1.1 基本身份验证方案
    • URL 授权模块,支持基于用户和角色规则的访问控制
  3. 若要添加这些模块,请将模块加载条目添加到 <system.webServer>/<globalModules> 节,然后启用 <system.webServer>/<modules> 节中的模块,就像我们之前对静态文件处理程序和目录浏览器所做的那样。

    注意

    这一次,我们不需要将任何内容添加到 <system.webServer>/<handlers> 节,因为这些模块不提供请求处理 – 它们只向所有请求提供请求服务。 添加以下粗体项后,你的最终配置将如下所示:

    <globalModules>
        <add name="AnonymousAuthenticationModule" image="%windir%\system32\inetsrv\authanon.dll" /> 
        <add name="StaticFileModule" image="%windir%\system32\inetsrv\static.dll" /> 
        <add name="DirectoryListingModule" image="%windir%\system32\inetsrv\dirlist.dll" /> 
        <add name="UrlAuthorizationModule" image="%windir%\System32\inetsrv\urlauthz.dll" />      
        <add name="BasicAuthenticationModule" image="%windir%\System32\inetsrv\authbas.dll" /> 
    </globalModules> 
    
    <modules> 
        <add name="AnonymousAuthenticationModule" /> 
        <add name="StaticFileModule" /> 
        <add name="DirectoryListingModule" /> 
        <add name="BasicAuthenticationModule" /> 
        <add name="UrlAuthorizationModule" /> 
    </modules>
    

    若要使用添加的功能,我们需要对其进行配置。

  4. 启用基本身份验证服务。 导航到 <basicAuthentication> 元素,并将启用的属性设置为 true:

    <basicAuthentication enabled="true" />
    
  5. 禁用匿名身份验证。 导航到 <anonymousAuthentication> 元素,并将启用的属性设置为 false:

    <anonymousAuthentication enabled="false" userName="IUSR" />
    

    这将禁用匿名身份验证,并要求在授予访问权限之前通过基本身份验证模块成功验证用户身份。

  6. 保存 applicationHost.config 文件。

  7. 打开 Internet Explorer,并通过请求以下 URL 重复对目录的请求:

    http://localhost
    

    这将请求目录列表。 由于浏览器尚未对我们进行身份验证,因此 URL 授权模块拒绝该请求。 基本身份验证模块截获拒绝,并向浏览器触发返回基本身份验证质询,导致浏览器显示基本身份验证登录对话框。

  8. 使用无效凭据登录。 请求被拒绝,请求再次提示输入凭据。

  9. 使用用于登录计算机的管理员帐户登录。 此时将显示目录列表,表明你已成功向服务器添加身份验证和授权功能。

总结

本文介绍了服务器的组件化性质,研究了所提供的 IIS 功能,并解释了如何仅使用用户可能需要的服务来构建自定义 Web 服务器。

在再次使用服务器之前,请撤消在本演练中执行的服务器配置更改。 如果你之前创建了备份,请通过从命令行运行 %windir%\system32\inetsrv\appcmd restore backup initial 将其还原。

有关详细信息,请参阅以下链接: