如何从C# WinForms应用调用受Membership验证的Web Handler
我有一个ASP.NET Web Forms站点,通过多个Web Handler暴露数据库功能,这些Handler所在文件夹依赖Microsoft MembershipProvider模型做用户身份验证,已认证的站点用户访问完全正常。但主机服务商修改SQL Server政策后,数据库无法跨域访问,因此我编写了C# WinForms应用来管理站点数据,唯一途径就是调用站点的Web Handler。
若Handler放在未授权文件夹中,WinForms应用可正常调用;但将Handler移至受保护文件夹后,WebRequest/WebClient会被直接重定向到登录页。
我使用的RestClient类代码如下:
using System; using System.IO; using System.Net; namespace MCTDesk.Web { public enum httpVerb { GET, POST, PUT, DELETE } public enum authenticationType { Basic, NTLM } public enum autheticationTechnique { RollYourOwn, NetworkCredential } class RestClient { public string endPoint { get; set; } public httpVerb httpMethod { get; set; } public authenticationType authType { get; set; } public autheticationTechnique authTech { get; set; } public string userName { get; set; } public string userPassword { get; set; } public RestClient ( ) { endPoint = string.Empty; httpMethod = httpVerb.GET; } public string makeRequest ( ) { string strResponseValue = string.Empty; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(endPoint); request.Method = httpMethod.ToString ( ); String authHeader = System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(userName + ":" + userPassword)); request.Headers.Add ( "Authorization", authType.ToString ( ) + " " + authHeader ); HttpWebResponse response = null; try { response = ( HttpWebResponse ) request.GetResponse ( ); //Process the response stream... (could be JSON, XML or HTML etc...) using ( Stream responseStream = response.GetResponseStream ( ) ) { if ( responseStream != null ) { using ( StreamReader reader = new StreamReader ( responseStream ) ) { strResponseValue = reader.ReadToEnd ( ); } } } } catch ( Exception ex ) { strResponseValue = "{\"errorMessages\":[\"" + ex.Message.ToString ( ) + "\"],\"errors\":{}}"; } finally { if ( response != null ) { ( ( IDisposable ) response ).Dispose ( ); } } return strResponseValue; } } }
调用代码:
private void LoadStats ( ) { RestClient rClient = new RestClient { endPoint = "https://[my site URL].com/handlers/gettodaystats.ashx", authTech = autheticationTechnique.RollYourOwn, authType = authenticationType.Basic, userName = "<username>", userPassword = "<password>" }; string strResponse = string.Empty; strResponse = rClient.makeRequest ( ); MessageBox.Show ( strResponse ); }
此前提问被判定为重复,但现有方案均无效,请问问题出在哪里?
核心问题:你使用的HTTP Basic认证和ASP.NET Web Forms默认的MembershipProvider表单认证完全不兼容,ASP.NET的表单认证系统不会识别你添加的Basic认证头,因此直接将请求重定向到登录页。
提供两种可行解决路径:
路径1:让Web Handler支持Basic认证(推荐)
修改ASP.NET站点配置,让受保护文件夹内的Handler支持Basic认证,并关联现有MembershipProvider验证用户身份。
- 在站点根Web.config的
<system.web>节点下启用Basic认证模块:
<authentication mode="Forms"> <!-- 保留原有Forms认证配置 --> <forms loginUrl="~/Login.aspx" timeout="2880" /> </authentication> <authorization> <deny users="?" /> </authorization> <httpModules> <add name="BasicAuthenticationModule" type="System.Web.Security.BasicAuthenticationModule" /> </httpModules>
- 针对Handler所在文件夹单独配置Basic认证(可通过
<location>节点实现):
<location path="handlers"> <system.web> <authorization> <deny users="?" /> </authorization> <authentication mode="Basic"> <basicAuthentication providerName="AspNetSqlMembershipProvider" realm="Your Site Realm" /> </authentication> </system.web> </location>
注意:若使用IIS 7及以上版本,需在IIS管理器中启用「Basic Authentication」功能,并确保应用池的.NET版本与站点匹配。
路径2:模拟表单认证流程
如果不想修改站点认证配置,可先模拟用户登录请求获取认证Cookie,后续调用Handler时携带该Cookie即可。
修改RestClient类添加Cookie支持:
using System; using System.IO; using System.Net; namespace MCTDesk.Web { public enum httpVerb { GET, POST, PUT, DELETE } public enum authenticationType { Basic, NTLM } public enum autheticationTechnique { RollYourOwn, NetworkCredential } class RestClient { public string endPoint { get; set; } public httpVerb httpMethod { get; set; } public authenticationType authType { get; set; } public autheticationTechnique authTech { get; set; } public string userName { get; set; } public string userPassword { get; set; } // 新增Cookie容器,保存认证Cookie private CookieContainer _cookieContainer = new CookieContainer(); public RestClient ( ) { endPoint = string.Empty; httpMethod = httpVerb.GET; } public string makeRequest(bool isLoginRequest = false) { string strResponseValue = string.Empty; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(endPoint); request.Method = httpMethod.ToString(); request.CookieContainer = _cookieContainer; if (isLoginRequest) { // 构造登录表单数据,字段名需与站点Login.aspx实际字段一致 string postData = $"UserName={Uri.EscapeDataString(userName)}&Password={Uri.EscapeDataString(userPassword)}&RememberMe=false"; byte[] data = System.Text.Encoding.UTF8.GetBytes(postData); request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = data.Length; using (Stream stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } } HttpWebResponse response = null; try { response = (HttpWebResponse)request.GetResponse(); // 保存响应中的Cookie到容器 _cookieContainer.Add(response.Cookies); using (Stream responseStream = response.GetResponseStream()) { if (responseStream != null) { using (StreamReader reader = new StreamReader(responseStream)) { strResponseValue = reader.ReadToEnd(); } } } } catch (Exception ex) { strResponseValue = "{\"errorMessages\":[\"" + ex.Message.ToString() + "\"],\"errors\":{}}"; } finally { response?.Dispose(); } return strResponseValue; } } }
调整调用流程,先登录获取Cookie再调用Handler:
private void LoadStats() { RestClient rClient = new RestClient(); // 第一步:发送登录请求获取认证Cookie rClient.endPoint = "https://[my site URL].com/Login.aspx"; rClient.httpMethod = httpVerb.POST; rClient.userName = "<username>"; rClient.userPassword = "<password>"; rClient.makeRequest(isLoginRequest: true); // 第二步:携带Cookie调用Handler rClient.endPoint = "https://[my site URL].com/handlers/gettodaystats.ashx"; rClient.httpMethod = httpVerb.GET; string strResponse = rClient.makeRequest(); MessageBox.Show(strResponse); }
注意:登录表单的字段名需与站点Login.aspx页面的实际字段ID一致(可通过查看登录页源码确认,可能是类似ctl00$MainContent$UserName的ASP.NET生成ID)。
内容的提问来源于stack exchange,提问作者RiverNet

