【问题标题】:IIS Hosted B2B WCF REST AuthenticationIIS 托管 B2B WCF REST 身份验证
【发布时间】:2020-04-27 07:37:51
【问题描述】:

WCF 服务提供了一个可供客户业务流程访问的 REST 端点。 我想要一个安全系统来验证传入请求中的用户名和密码,以确保它仅来自该客户。

从探索这个主题来看,基本身份验证似乎很合适,但它需要一个自定义 HTTPModule 和 web.config 中的一个条目才能以“B2B 方式”工作。

我的期望是: 1) HttpModule 将处理身份验证,我可以保持 IIS 配置不变。即我不必使用 IIS 管理器在站点级别打开基本身份验证。

2) 我对服务的测试 Postman 'No Auth' POST 应该在 Auth 关闭时工作,而在 Auth 打开时失败 401。 (开启和关闭由 HttpModule 及其 web.config 关联值控制)

3) 启用 HttpModule Auth 后,在 Postman 中为 POST 启用 Basic Auth 应该可以让它再次工作。

因此,如果 Basic Auth 不是最佳选择,请提出建议。

我的问题是,一旦我将 HTTPModule 条目添加到 web.config 中的“System.Web”元素,网站在访问时就会变为 500。

HTTP 模块和 Web.config 紧随其后。 (HTTP 模块由各种来源拼凑而成。)

来源: HTTP 模块

public class BasicAuthHttpModule:IHttpModule
{
    private static readonly log4net.ILog Log = log4net.LogManager.GetLogger
        (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    public void Init(HttpApplication app)
    {

        app.AuthenticateRequest += OnApplicationAuthenticateRequest;
        app.EndRequest += OnApplicationEndRequest;
        app.AcquireRequestState += new EventHandler(app_AcquireRequestState);
        app.PostAcquireRequestState += new EventHandler(app_PostAcquireRequestState);
    }

    private void app_AcquireRequestState(object o, EventArgs ea)
    {
        HttpApplication httpApp = (HttpApplication)o;
        HttpContext ctx = HttpContext.Current;
        ctx.Response.Write(" Executing AcquireRequestState ");
    }

    private void app_PostAcquireRequestState(object o, EventArgs ea)
    {
        HttpApplication httpApp = (HttpApplication)o;
        HttpContext ctx = HttpContext.Current;
        ctx.Response.Write(" Executing PostAcquireRequestState ");
    }

    private static void SetPrincipal(IPrincipal principal)
    {
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
        }
    }
    /// <summary>
    /// Validate the user and password
    /// which are stored in Web.Config
    /// </summary>
    /// <param name="username"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    private static bool CheckPassword(string username, string password)
    {
        if(Log.IsDebugEnabled)
            Log.Debug($"Auth attempt User {username} password {password}");
        string sUser = ConfigurationManager.AppSettings["ReceiptUser"];
        string sPassword = ConfigurationManager.AppSettings["ReceiptPassword"];
        bool result = username == sUser && password == sPassword;
        if(Log.IsDebugEnabled)
            Log.Debug("Checkpassword result is " + result);
        return result;
    }
    private static void AuthenticateUser(string credentials)
    {
        try
        {
            if (Log.IsDebugEnabled)
            {
                Log.Debug($"Authentication Attempt credentials {credentials}");
            }

            var encoding = Encoding.GetEncoding("iso-8859-1");
            credentials = encoding.GetString(Convert.FromBase64String(credentials));

            int separator = credentials.IndexOf(':');
            string name = credentials.Substring(0, separator);
            string password = credentials.Substring(separator + 1);
            if(Log.IsDebugEnabled)
                Log.Debug($"About to Check Password username{name} password{password}");
            if (CheckPassword(name, password))
            {
                if (Log.IsDebugEnabled)
                    Log.Debug($"Password Checked OK username{name} password{password}");
                var identity = new GenericIdentity(name);
                SetPrincipal(new GenericPrincipal(identity, null));
            }
            else
            {
                if (Log.IsDebugEnabled)
                    Log.Debug($"Password check failed return 401 username{name} password{password}");
                // Invalid username or password.
                HttpContext.Current.Response.StatusCode = 401;
            }
        }
        catch (FormatException)
        {
            // Credentials were not formatted correctly.
            HttpContext.Current.Response.StatusCode = 401;
            Log.Error("Credentials not correctly formatted");
        }
    }
    /// <summary>
    /// Extract the Authorization Header.
    /// If Header exists parse it  and authenticate it
    /// If it doesn't exist then check if basic auth is turned on.
    /// If it is turned on throw a 401.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
    {
        if(Log.IsDebugEnabled)
            Log.Debug("Authentication Hit");
        var request = HttpContext.Current.Request;
        var authHeader = request.Headers["Authorization"];
        if (authHeader != null)
        {
            if (Log.IsDebugEnabled)
                Log.Debug($"OnApplicationAuthenticateRequest request.Headers[\"Authorization\"] is {authHeader} ");
            var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

            // RFC 2617 sec 1.2, "scheme" name is case-insensitive
            if (authHeaderVal.Scheme.Equals("basic",
                    StringComparison.OrdinalIgnoreCase) &&
                authHeaderVal.Parameter != null)
            {
                if (Log.IsDebugEnabled)
                    Log.Debug("OnApplicationAuthenticateRequest about to authenticate basic user parameter  " + authHeaderVal.Parameter);
                AuthenticateUser(authHeaderVal.Parameter);
            }
        }
        else
        {
            bool basicAuth = bool.Parse(ConfigurationManager.AppSettings["BasicAuth"]);
            if (!basicAuth)
            {
                if (Log.IsDebugEnabled)
                    Log.Debug("OnApplicationAuthenticateRequest request.Headers[\"Authorization\"] is null and basicAuth is not turned on in Web.Config ");
                return;
            }

            if (Log.IsDebugEnabled)
                Log.Debug("OnApplicationAuthenticateRequest request.Headers[\"Authorization\"] is null ");
            HttpContext.Current.Response.StatusCode = 401;
        }
    }

    // If the request was unauthorized, add the WWW-Authenticate header 
    // to the response.
    private static void OnApplicationEndRequest(object sender, EventArgs e)
    {
        string Realm = "WCCReceipt";
        var response = HttpContext.Current.Response;
        if (response.StatusCode == 401)
        {
            response.Headers.Add("WWW-Authenticate",
                string.Format("Basic realm=\"{0}\"", Realm));
        }
    }

    public void Dispose()
    {

    }
}

Web.Config

    <?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.15.0, Culture=neutral" />
  </configSections>
  <connectionStrings>
    <add name="xxxxx" connectionString="Data Source=xxxxxx;Initial Catalog=xxxxxx;User ID=xxxxxx;Password=xxxxxxx" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    <add key="OriginID" value="19" />
    <add key="Diag" value="true" />
    <add key="Receiptuser" value="xxxx"/>
    <add key="ReceiptPassword" value ="xxxxx"/>
    <add key="BasicAuth" value="false"/>
    <add key="CheckPayableTicket" value="false"/>
  </appSettings>
  **<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
    <httpModules>
      <add name="BasicAuthHttpModule" type="xxxxx.BasicAuthHttpModule"/>
      <!--<add name="BasicAuthHttpModule" type="WebHostBasicAuth.Modules.BasicAuthHttpModule, xxxxx"/>-->
    </httpModules>
  </system.web>**
  <system.serviceModel>
    <bindings>
      <webHttpBinding>
        <binding name="webHttpBindingJSON" receiveTimeout="00:01:00">
          <security mode="Transport"/>
        </binding>
      </webHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="ServiceBehaviour" name="xxxxx.xxxxx">
        <endpoint address="" behaviorConfiguration="web" binding="webHttpBinding" contract="xxxxx.Ixxxxx" />
        <endpoint address="JSON" behaviorConfiguration="RESTJSONEndPointBehavior" binding="webHttpBinding" bindingConfiguration="webHttpBindingJSON" name="JSON" contract="xxxxxx.Ixxxxx" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp />
        </behavior>
        <behavior name="RESTJSONEndPointBehavior">
          <webHttp  defaultOutgoingResponseFormat="Json" />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehaviour">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <log4net debug="false">
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
      <threshold value="ALL" />
      <file value="Log/Log.txt" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="5" />
      <maximumFileSize value="5MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %level %logger[%thread] - %message%newline" />
      </layout>
    </appender>
    <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
      <threshold value="ALL" />
      <logName value="NetVendor" />
      <applicationName value="NetVendor" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%level %logger[%thread] - %message%newline" />
      </layout>
    </appender>
    <root>
      <priority value="ALL" />
      <appender-ref ref="RollingFileAppender" />
      <appender-ref ref="EventLogAppender" />
    </root>
    <category name="DesktopLogger.Form1">
      <priority value="ALL" />
    </category>
  </log4net>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

【问题讨论】:

    标签: wcf


    【解决方案1】:

    当网站托管在 IIS 中时,Basic authentication 由 IIS 的BasicAutenticationModule 提供。它总是使用服务器的 Windows 帐户对客户端进行身份验证。因此,我认为我们的自定义基本身份验证模块不会起作用。
    这是 WCF Rest web 服务中自定义身份验证的另一种解决方案,希望对您有用。
    How to Implement custom authentication in WCF service
    如果有什么可以帮助的,请随时告诉我。

    【讨论】:

    • 嗨,亚伯拉罕,我一直在尝试很多不同的解决方案。没有运气。也许我想多了。您说基本身份验证针对服务器的 Windows 帐户。如果我们将这些凭据提供给唯一的用户,那么只需在 IIS 中启用基本身份验证(并禁用匿名)就可以了?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    • 2011-07-05
    • 1970-01-01
    • 2015-04-14
    • 2011-07-22
    • 2011-03-27
    • 1970-01-01
    相关资源
    最近更新 更多