【发布时间】: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