IIS托管的B2B WCF REST身份验证

时间:2020-04-27 07:37:51

标签: wcf

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

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

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

2)我对服务的测试邮递员“无身份验证” POST在Auth关闭时应该可以工作,而在Auth开启时失败401。 (由HttpModule及其web.config关联值控制打开和关闭)

3)在启用HttpModule Auth的情况下,在Postman中启用POST的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>

1 个答案:

答案 0 :(得分:0)

当网站托管在IIS中时,Basic authentication由IIS的BasicAutenticationModule提供。并且始终使用服务器的Windows帐户对客户端进行身份验证。因此,我认为我们的自定义基本身份验证模块无法正常工作。
这是WCF Rest Web服务中自定义身份验证的另一种解决方案,希望它对您有用。
How to Implement custom authentication in WCF service
随时让我知道是否有什么可以帮助您的。