【问题标题】:Turning off ASP.Net WebForms authentication for one sub-directory关闭一个子目录的 ASP.Net WebForms 身份验证
【发布时间】:2011-06-04 17:31:20
【问题描述】:

我有一个包含 WebForms 和 MVC 页面的大型企业应用程序。它具有我不想更改的现有身份验证和授权设置。

WebForms 身份验证在 web.config 中配置:

 <authentication mode="Forms">
  <forms blah... blah... blah />
 </authentication>

 <authorization>
  <deny users="?" />
 </authorization>

到目前为止相当标准。我有一个 REST 服务,它是这个大型应用程序的一部分,我想对这个服务使用 HTTP 身份验证。

因此,当用户尝试从 REST 服务获取 JSON 数据时,它会返回 HTTP 401 状态和 WWW-Authenticate 标头。如果他们以正确格式的 HTTP Authorization 响应进行响应,它将允许他们进入。

问题是 WebForms 在低级别覆盖了这个 - 如果你返回 401(未经授权),它会用 302(重定向到登录页面)覆盖它。这在浏览器中很好,但对 REST 服务无用。

我想关闭 web.config 中的身份验证设置,覆盖“rest”文件夹:

 <location path="rest">
  <system.web>
   <authentication mode="None" />
   <authorization><allow users="?" /></authorization>
  </system.web>
 </location>

authorisation 位工作正常,但 authentication 行 (&lt;authentication mode="None" /&gt;) 会导致异常:

在应用程序级别之外使用注册为 allowDefinition='MachineToApplication' 的部分是错误的。

我在应用程序级别配置它 - 它位于根 web.config 中 - 并且该错误是针对子目录中的 web.configs 的。

如何覆盖身份验证,以便站点的所有其余部分都使用 WebForms 身份验证,而这个目录不使用?

这类似于另一个问题:401 response code for json requests with ASP.NET MVC,但我不是在寻找相同的解决方案 - 我不想只是删除 WebForms 身份验证并在全球范围内添加新的自定义代码,风险和工作量都很大涉及。我只想更改配置中的一个目录。

更新

我想设置一个 Web 应用程序,并且我希望所有 WebForms 页面和 MVC 视图都使用 WebForms 身份验证。我希望一个目录使用基本的 HTTP 身份验证。

请注意,我说的是身份验证,而不是授权。我希望 REST 调用在 HTTP 标头中带有用户名和密码,并且我希望 WebForm 和 MVC 页面带有来自 .Net 的身份验证 cookie - 在任何一种情况下,授权都是针对我们的数据库进行的。

我不想重写 WebForms 身份验证并滚动我自己的 cookie - 将 HTTP 授权的 REST 服务添加到应用程序的唯一方法似乎很荒谬。

我无法添加额外的应用程序或虚拟目录 - 它必须作为一个应用程序。

【问题讨论】:

标签: asp.net http authentication web-config http-status-code-401


【解决方案1】:

在 .NET 4.5 中,您现在可以设置

Response.SuppressFormsAuthenticationRedirect = true

查看此页面:https://msdn.microsoft.com/en-us/library/system.web.httpresponse.suppressformsauthenticationredirect.aspx

【讨论】:

  • 请注意,为了抑制它,您需要将值设置为true
【解决方案2】:

我发现自己遇到了同样的问题,以下文章为我指明了正确的方向:http://msdn.microsoft.com/en-us/library/aa479391.aspx

MADAM 完全符合您的要求,具体而言,您可以配置 FormsAuthenticationDispositionModule 以使表单身份验证“诡计”静音,并阻止其将响应代码从 401 更改为 302。这应该会导致您的其余客户端收到正确的身份验证挑战。

MADAM 下载页面:http://www.raboof.com/projects/madam/

在我的例子中,REST 调用是在“API”中对控制器(这是一个基于 MVC 的应用程序)进行的 区域。使用以下配置设置 MADAM 鉴别器:

<formsAuthenticationDisposition>
  <discriminators all="1">
    <discriminator type="Madam.Discriminator">
      <discriminator
          inputExpression="Request.Url"
          pattern="api\.*" type="Madam.RegexDiscriminator" />
    </discriminator>
  </discriminators>
</formsAuthenticationDisposition>

那么您所要做的就是将 MADAM 模块添加到您的 web.config 中

<modules runAllManagedModulesForAllRequests="true">
  <remove name="WebDAVModule" /> <!-- allow PUT and DELETE methods -->
  <add name="FormsAuthenticationDisposition" type="Madam.FormsAuthenticationDispositionModule, Madam" />
</modules>

记得将有效部分添加到 web.config 中(所以没有让我粘贴代码),您可以从下载的 web 项目中获取示例。

通过此设置,对以“API/”开头的 URL 发出的任何请求都将获得 401 响应,而不是表单身份验证生成的 301。

【讨论】:

  • 正则表达式 api\.* 不匹配 'api' 后跟一个句点多次吗?我想你的意思是api/.*
【解决方案3】:

我已经以混乱的方式解决了这个问题 - 通过在 global.asax 中为所有现有页面欺骗表单身份验证。

我还没有完全做到这一点,但它是这样的:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    // lots of existing web.config controls for which webforms folders can be accessed
    // read the config and skip checks for pages that authorise anon users by having
    // <allow users="?" /> as the top rule.

    // check local config
    var localAuthSection = ConfigurationManager.GetSection("system.web/authorization") as AuthorizationSection;

    // this assumes that the first rule will be <allow users="?" />
    var localRule = localAuthSection.Rules[0];
    if (localRule.Action == AuthorizationRuleAction.Allow &&
        localRule.Users.Contains("?"))
    {
        // then skip the rest
        return;
    }

    // get the web.config and check locations
    var conf = WebConfigurationManager.OpenWebConfiguration("~");
    foreach (ConfigurationLocation loc in conf.Locations)
    {
        // find whether we're in a location with overridden config
        if (this.Request.Path.StartsWith(loc.Path, StringComparison.OrdinalIgnoreCase) ||
            this.Request.Path.TrimStart('/').StartsWith(loc.Path, StringComparison.OrdinalIgnoreCase))
        {
            // get the location's config
            var locConf = loc.OpenConfiguration();
            var authSection = locConf.GetSection("system.web/authorization") as AuthorizationSection;
            if (authSection != null)
            {
                // this assumes that the first rule will be <allow users="?" />
                var rule = authSection.Rules[0];
                if (rule.Action == AuthorizationRuleAction.Allow &&
                    rule.Users.Contains("?"))
                {
                    // then skip the rest
                    return;
                }
            }
        }
    }

    var cookie = this.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (cookie == null ||
        string.IsNullOrEmpty(cookie.Value))
    {
        // no or blank cookie
        FormsAuthentication.RedirectToLoginPage();
    }

    // decrypt the 
    var ticket = FormsAuthentication.Decrypt(cookie.Value);
    if (ticket == null ||
        ticket.Expired)
    {
        // invalid cookie
        FormsAuthentication.RedirectToLoginPage();
    }

    // renew ticket if needed
    var newTicket = ticket;
    if (FormsAuthentication.SlidingExpiration)
    {
        newTicket = FormsAuthentication.RenewTicketIfOld(ticket);
    }

    // set the user so that .IsAuthenticated becomes true
    // then the existing checks for user should work
    HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(newTicket), newTicket.UserData.Split(','));

}

我对这个修复并不满意 - 这似乎是一个可怕的黑客和轮子的重新发明,但看起来这是我的 Forms 身份验证页面和 HTTP 身份验证 REST 服务的唯一方法在同一个应用程序中工作。

【讨论】:

  • 是的,这就是使两种模式一起工作所必须做的(在 HttpApplication 实例中或在 http 模块中)。对不起,你必须走这条路。我仍然对必须将 REST 服务保留在同一个应用程序中感到好奇。你能强调为什么你必须这样做吗?我发现这是一个有趣的约束。
  • @arcain - 我们有很多 IIS 应用程序已经在运行,每个应用程序都需要在内存中保留相当数量的东西,最值得注意的是动态编译插件的实例。我希望 REST 服务使用相同的资源,并且不需要我们的托管人员必须创建和维护双倍的 IIS 应用程序。
【解决方案4】:

在查看了您对我之前的回答的 cmets 之后,我想知道您是否可以让您的 Web 应用程序自动在您的 REST 目录上部署应用程序。这将使您能够获得第二个应用程序的好处,并且还可以减轻系统管理员的部署负担。

我的想法是,您可以将例程放入 global.asax 的 Application_Start 方法中,以检查 REST 目录是否存在,并且它还没有与之关联的应用程序。如果测试返回 true,则执行将新应用程序关联到 REST 目录的过程。

我的另一个想法是您可以使用WIX(或其他部署技术)来构建一个安装包,您的管理员可以运行该安装包来创建应用程序,但我认为这不像配置应用程序那样自动它的依赖关系。

下面,我包含了一个示例实现,它检查 IIS 是否有给定目录,如果还没有应用程序,则将应用程序应用到它。该代码已使用 IIS 7 进行了测试,但也应该可以在 IIS 6 上运行。

//This is part of global.asax.cs
//This approach may require additional user privileges to query IIS

//using System.DirectoryServices;
//using System.Runtime.InteropServices;

protected void Application_Start(object sender, EventArgs evt)
{
  const string iisRootUri = "IIS://localhost/W3SVC/1/Root";
  const string restPhysicalPath = @"C:\inetpub\wwwroot\Rest";
  const string restVirtualPath = "Rest";

  if (!Directory.Exists(restPhysicalPath))
  {
    // there is no rest path, so do nothing
    return;
  }

  using (var root = new DirectoryEntry(iisRootUri))
  {
    DirectoryEntries children = root.Children;

    try
    {
      using (DirectoryEntry rest = children.Find(restVirtualPath, root.SchemaClassName))
      {
        // the above call throws an exception if the vdir does not exist
        return;
      }
    }
    catch (COMException e)
    {
      // something got unlinked incorrectly, kill the vdir and application
      foreach (DirectoryEntry entry in children)
      {
        if (string.Compare(entry.Name, restVirtualPath, true) == 0)
        {
          entry.DeleteTree();
        }     
      }
    }
    catch (DirectoryNotFoundException e)
    {
      // the vdir and application do not exist, add them below
    }

    using (DirectoryEntry rest = children.Add(restVirtualPath, root.SchemaClassName))
    {
      rest.CommitChanges();
      rest.Properties["Path"].Value = restPhysicalPath;
      rest.Properties["AccessRead"].Add(true);
      rest.Properties["AccessScript"].Add(true);
      rest.Invoke("AppCreate2", true);
      rest.Properties["AppFriendlyName"].Add(restVirtualPath);
      rest.CommitChanges();
    }
  }
}

部分代码来自here。祝你的应用好运!

【讨论】:

    【解决方案5】:

    我能够在以前的项目中使用它,但它确实需要使用 HTTP 模块来执行自定义基本身份验证,因为帐户验证是针对数据库而不是 Windows。

    我按照您的要求设置了测试,在测试网站的根目录使用了一个 web 应用程序,以及一个包含 REST 服务的文件夹。根应用程序的配置被配置为拒绝所有访问:

    <authentication mode="Forms">
      <forms loginUrl="Login.aspx" timeout="2880" />
    </authentication>
    <authorization>
      <deny users="?"/>
    </authorization>
    

    然后我必须在 IIS 中为 REST 文件夹创建一个应用程序,并将 web.config 文件放入 REST 文件夹。在该配置中,我指定了以下内容:

    <authentication mode="None"/>
    <authorization>
      <deny users="?"/>
    </authorization>
    

    我还必须在 REST 目录的配置中的适当位置连接 http 模块。此模块必须进入 REST 目录下的 bin 目录。我使用了 Dominick Baier 的自定义基本身份验证模块,该代码位于 here。该版本更特定于 IIS 6,但是在 codeplex 上也有一个 IIS 7 版本,但我没有测试过那个版本(警告: IIS6 版本没有相同的程序集名称和命名空间为 IIS7 版本。)我真的很喜欢这个基本的身份验证模块,因为它直接插入 ASP.NET 的成员资格模型。

    最后一步是确保只允许匿名访问 IIS 中的根应用程序和 REST 应用程序。

    为了完整起见,我在下面包含了完整的配置。测试应用程序只是一个从 VS 2010 生成的 ASP.NET Web 表单应用程序,它使用 AspNetSqlProfileProvider 作为成员资格提供程序;这是配置:

    <?xml version="1.0"?>
    
    <configuration>
      <connectionStrings>
        <add name="ApplicationServices"
          connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;Database=sqlmembership;"
        providerName="System.Data.SqlClient" />
      </connectionStrings>
    
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
    
        <authentication mode="Forms">
          <forms loginUrl="~/Account/Login.aspx" timeout="2880" />
        </authentication>
    
        <authorization>
          <deny users="?"/>
        </authorization>
    
        <membership>
          <providers>
            <clear/>
            <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
              enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
              maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
            applicationName="/" />
          </providers>
        </membership>
    
        <profile>
          <providers>
            <clear/>
            <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/"/>
          </providers>
        </profile>
    
        <roleManager enabled="false">
          <providers>
            <clear/>
            <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" />
            <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
          </providers>
        </roleManager>
    
      </system.web>
    
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
      </system.webServer>
    </configuration>
    

    REST 目录包含一个从 VS 2010 生成的空 ASP.NET 项目,我将一个 ASPX 文件放入其中,但是 REST 文件夹的内容必须是新的项目。在目录有与之关联的应用程序之后放入配置文件应该可以工作。该项目的配置如下:

    <?xml version="1.0"?>
    <configuration>
      <configSections>
        <section name="customBasicAuthentication" type="Thinktecture.CustomBasicAuthentication.CustomBasicAuthenticationSection, Thinktecture.CustomBasicAuthenticationModule"/>
      </configSections>
      <customBasicAuthentication
        enabled="true"
        realm="testdomain"
        providerName="AspNetSqlMembershipProvider"
        cachingEnabled="true"
        cachingDuration="15"
      requireSSL="false" />
    
      <system.web>
        <authentication mode="None"/>
        <authorization>
          <deny users="?"/>
        </authorization>
    
        <compilation debug="true" targetFramework="4.0" />
        <httpModules>
          <add name="CustomBasicAuthentication" type="Thinktecture.CustomBasicAuthentication.CustomBasicAuthenticationModule, Thinktecture.CustomBasicAuthenticationModule"/>
        </httpModules>
      </system.web>
    </configuration>
    

    我希望这能满足您的需求。

    【讨论】:

    • 干杯,这是有用的信息,但不是我真正需要的解决方案。正如我在问题中所说的,我已经拥有可以使用的基本 HTTP 授权,问题是让它在与 Forms 身份验证页面相同的 IIS 应用程序中工作。
    • 我的解决方案需要两个应用程序(它们可以在同一个应用程序池中)才能工作,因为表单身份验证与所有其他身份验证类型是互斥的,除非您滚动自己的混合模式模块来同时执行这两项操作。您只能在应用程序级别覆盖身份验证模式。所以,我相信你的问题的答案是你不能做你想做的事,除非你使用第二个应用程序,这样你就可以覆盖你的父网站的配置。
    • 我们有大量的 IIS 应用程序(大约 100 个左右)在同一台服务器上运行此代码 - 这对于我们的托管人员来说已经足够令人头疼了,因为它不会加倍。我认为推出我自己的混合模块可能是唯一的方法,但对于应该简单的事情来说,这是一个丑陋的解决方案。
    • 如果内部应用程序想要使用一些资源,例如一些 EF 模型。你怎么能把它们联系起来?
    【解决方案6】:

    这可能不是最优雅的解决方案,但我认为这是一个好的开始

    1) 创建一个 HttpModule。

    2) 处理 AuthenticateRequest 事件。

    3) 在事件处理程序中检查请求是否针对您要允许访问的目录。

    4)如果是则手动设置身份验证cookie:(或者看看你现在是否可以找到另一种方式,因为你已经控制并且尚未发生身份验证)

    FormsAuthentication.SetAuthCookie("Anonymous", false);
    

    5) 哦,差点忘了,如果请求不是针对您要授予访问权限的目录,您需要确保清除 auth cookie。

    【讨论】:

      【解决方案7】:

      如果“rest”只是根目录中的一个文件夹,那么您几乎就在那里: 删除身份验证行,即

      <location path="rest">
        <system.web>
            <authorization>
              <allow users="*" />
            </authorization>
        </system.web>
       </location>
      

      或者,您可以将 web.config 添加到您的 rest 文件夹中,然后就可以了:

      <system.web>
           <authorization>
                <allow users="*" />
           </authorization>
      </system.web>
      

      检查this 一个。

      【讨论】:

      • 是的,'rest' 只是其中包含我的 REST 服务的文件夹 - 我可以更改 &lt;authorization&gt; 就好了。问题是&lt;authentication mode="None" /&gt; 行 - 如果我把它拿出来,我的 web.config 不会抛出错误,但没有它,所有 401 HTTP 授权都会被 WebForms 设置吞噬。基本上我需要 &lt;authentication mode="None" /&gt; 才能使 HTTP WWW-Authenticate 工作,但它会在 web.config 中引发错误,无论它是文件夹之一还是根目录。
      • 您的子文件夹不能有身份验证部分。在这种情况下,您只需将您的 rest 文件夹转换为虚拟目录,并使用它自己的 web.config 进行身份验证和授权。
      • 这是不可能的,因为它是同一个 IIS 应用程序的所有部分 - 我可以更改根 web.config,因此子文件夹配置无法覆盖它应该不是问题。问题中的示例位于根 web.config 中,因此应该有解决方法。
      • 我不确定如何绕过您的场景,但由于位置中的 ,您得到的错误是 100%。除非您的 rest 文件夹配置为应用程序,否则这是您无法拥有的。
      • 是的,我的错误是由于&lt;authentication mode="None" /&gt; - 我实际上在问题中说明了这一点。实际的问题是:我该如何解决这个问题? .Net 无法做到这一点似乎很荒谬。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-22
      • 1970-01-01
      • 2017-12-07
      • 1970-01-01
      • 2015-10-15
      • 2013-08-27
      • 1970-01-01
      相关资源
      最近更新 更多