结合表单身份validation和基本身份validation

我有一些核心ASP代码,我希望通过安全网页(使用表单身份validation)和通过Web服务(使用基本身份validation)公开。

我提出的解决方案似乎有效,但我在这里遗漏了什么吗?

首先,整个站点在HTTPS下运行。

站点设置为在web.config中使用表单身份validation

      

然后我覆盖Global.asax中的AuthenticateRequest,以在Web服务页面上触发基本身份validation:

 void Application_AuthenticateRequest(object sender, EventArgs e) { //check if requesting the web service - this is the only page //that should accept Basic Authentication HttpApplication app = (HttpApplication)sender; if (app.Context.Request.Path.StartsWith("/Service/MyService.asmx")) { if (HttpContext.Current.User != null) { Logger.Debug("Web service requested by user " + HttpContext.Current.User.Identity.Name); } else { Logger.Debug("Null user - use basic auth"); HttpContext ctx = HttpContext.Current; bool authenticated = false; // look for authorization header string authHeader = ctx.Request.Headers["Authorization"]; if (authHeader != null && authHeader.StartsWith("Basic")) { // extract credentials from header string[] credentials = extractCredentials(authHeader); // because i'm still using the Forms provider, this should // validate in the same way as a forms login if (Membership.ValidateUser(credentials[0], credentials[1])) { // create principal - could also get roles for user GenericIdentity id = new GenericIdentity(credentials[0], "CustomBasic"); GenericPrincipal p = new GenericPrincipal(id, null); ctx.User = p; authenticated = true; } } // emit the authenticate header to trigger client authentication if (authenticated == false) { ctx.Response.StatusCode = 401; ctx.Response.AddHeader( "WWW-Authenticate", "Basic realm=\"localhost\""); ctx.Response.Flush(); ctx.Response.Close(); return; } } } } private string[] extractCredentials(string authHeader) { // strip out the "basic" string encodedUserPass = authHeader.Substring(6).Trim(); // that's the right encoding Encoding encoding = Encoding.GetEncoding("iso-8859-1"); string userPass = encoding.GetString(Convert.FromBase64String(encodedUserPass)); int separator = userPass.IndexOf(':'); string[] credentials = new string[2]; credentials[0] = userPass.Substring(0, separator); credentials[1] = userPass.Substring(separator + 1); return credentials; } 

.Net 4.5有一个新的Response属性: SuppressFormsAuthenticationRedirect 。 设置为true时,它会阻止将401响应重定向到网站的登录页面。 您可以在global.asax.cs中使用以下代码段来启用基本身份validation,例如/ HealthCheck文件夹。

  ///  /// Authenticates the application request. /// Basic authentication is used for requests that start with "/HealthCheck". /// IIS Authentication settings for the HealthCheck folder: /// - Windows Authentication: disabled. /// - Basic Authentication: enabled. ///  /// The source of the event. /// A  that contains the event data. protected void Application_AuthenticateRequest(object sender, EventArgs e) { var application = (HttpApplication)sender; if (application.Context.Request.Path.StartsWith("/HealthCheck", StringComparison.OrdinalIgnoreCase)) { if (HttpContext.Current.User == null) { var context = HttpContext.Current; context.Response.SuppressFormsAuthenticationRedirect = true; } } } 

我根据OP的想法和Samuel Meacham的指示得到了一个解决方案。

在global.asax.cs中:

  protected void Application_AuthenticateRequest(object sender, EventArgs e) { if (DoesUrlNeedBasicAuth() && Request.IsSecureConnection) //force https before we try and use basic authentication { if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated) { _log.Debug("Web service requested by user " + HttpContext.Current.User.Identity.Name); } else { _log.Debug("Null user - use basic auth"); HttpContext ctx = HttpContext.Current; bool authenticated = false; // look for authorization header string authHeader = ctx.Request.Headers["Authorization"]; if (authHeader != null && authHeader.StartsWith("Basic")) { // extract credentials from header string[] credentials = extractCredentials(authHeader); //Lookup credentials (we'll do this in config for now) //check local config first var localAuthSection = ConfigurationManager.GetSection("apiUsers") as ApiUsersSection; authenticated = CheckAuthSectionForCredentials(credentials[0], credentials[1], localAuthSection); if (!authenticated) { //check sub config var webAuth = System.Web.Configuration.WebConfigurationManager.GetSection("apiUsers") as ApiUsersSection; authenticated = CheckAuthSectionForCredentials(credentials[0], credentials[1], webAuth); } } // emit the authenticate header to trigger client authentication if (authenticated == false) { ctx.Response.StatusCode = 401; ctx.Response.AddHeader("WWW-Authenticate","Basic realm=\"localhost\""); ctx.Response.Flush(); ctx.Response.Close(); return; } } } else { //do nothing } } ///  /// Detect if current request requires basic authentication instead of Forms Authentication. /// This is determined in the web.config files for folders or pages where forms authentication is denied. ///  public bool DoesUrlNeedBasicAuth() { HttpContext context = HttpContext.Current; string path = context.Request.AppRelativeCurrentExecutionFilePath; if (context.SkipAuthorization) return false; //if path is marked for basic auth, force it if (context.Request.Path.StartsWith(Request.ApplicationPath + "/integration", true, CultureInfo.CurrentCulture)) return true; //force basic //if no principal access was granted force basic auth //if (!UrlAuthorizationModule.CheckUrlAccessForPrincipal(path, context.User, context.Request.RequestType)) return true; return false; } private string[] extractCredentials(string authHeader) { // strip out the "basic" string encodedUserPass = authHeader.Substring(6).Trim(); // that's the right encoding Encoding encoding = Encoding.GetEncoding("iso-8859-1"); string userPass = encoding.GetString(Convert.FromBase64String(encodedUserPass)); int separator = userPass.IndexOf(':'); string[] credentials = new string[2]; credentials[0] = userPass.Substring(0, separator); credentials[1] = userPass.Substring(separator + 1); return credentials; } ///  /// Checks whether the given basic authentication details can be granted access. Assigns a GenericPrincipal to the context if true. ///  private bool CheckAuthSectionForCredentials(string username, string password, ApiUsersSection section) { if (section == null) return false; foreach (ApiUserElement user in section.Users) { if (user.UserName == username && user.Password == password) { Context.User = new GenericPrincipal(new GenericIdentity(user.Name, "Basic"), user.Roles.Split(',')); return true; } } return false; } 

允许访问的凭据存储在web.config的自定义部分中,但您可以按照自己的意愿存储。

上面的代码中需要HTTPS,但如果您愿意,可以删除此限制。 编辑但正如在评论中正确指出的那样,由于用户名和密码在纯文本中被编码和可见,因此这可能不是一个好主意。 当然,即使在此处使用HTTPS限制,您也无法阻止外部请求尝试使用不安全的HTTP并与观看流量的任何人共享其凭据。

现在,强制基本身份validation的路径在这里是硬编码的,但显然可以放在配置或其他来源中。 在我的例子中,’integration’文件夹设置为允许匿名用户。

这里有一行注释涉及CheckUrlAccessForPrincipal ,如果用户未通过表单身份validation登录,它将使用基本身份validation授予对站点上任何页面的访问权限。

使用Application_AuthenticateRequest而不是Application_AuthorizeRequest最终变得很重要,因为Application_AuthorizeRequest将强制进行基本身份validation,但无论如何都会重定向到Forms身份validation登录页面。 我没有成功通过在web.config中使用基于位置的权限来完成这项工作,但从未找到原因。 交换到Application_AuthenticateRequest做了伎俩,所以我把它留在了那里。

这样做的结果给我留下了一个文件夹,可以在通常使用表单身份validation的应用程序中使用基本身份validation通过HTTPS访问。 登录用户无论如何都可以访问该文件夹。

希望这可以帮助。

我认为你走的是正确的道路。 但是,我不确定您是否应该在身份validation请求中执行此操作。 这是在识别用户时,而不是在检查资源的权限时(稍后在授权请求中)。 首先,在您的web.config中,使用删除要使用基本身份validation的资源的表单身份validation。

Web.config文件

         

Global.asax.cs或任何地方(IHttpModule等)

然后,在Application_AuthorizeRequest ,不是硬编码特定处理程序或尝试解析URL以查看您是否在特定文件夹中,默认情况下,以下内容将使一切安全(表单auth 1st,基本身份validation,如果表单身份validation已经通过web.config中的设置删除。

 ///  /// Checks to see if the current request can skip authorization, either because context.SkipAuthorization is true, /// or because UrlAuthorizationModule.CheckUrlAccessForPrincipal() returns true for the current request/user/url. ///  ///  public bool DoesUrlRequireAuth() { HttpContext context = HttpContext.Current; string path = context.Request.AppRelativeCurrentExecutionFilePath; return context.SkipAuthorization || UrlAuthorizationModule.CheckUrlAccessForPrincipal( path, context.User, context.Request.RequestType); } void Application_AuthorizeRequest(object sender, EventArgs e) { if (DoesUrlRequireAuth()) { // request protected by forms auth } else { // do your http basic auth code here } } 

未经测试(仅在此处键入内联),但我已经完成了很多自定义成员资格提供程序,您的要求是完全可行的。

希望其中一些有用=)