iOS 不应在 HTTP POST 中的 NSURLSession 的 NSURL 中添加扩展名

标签 ios asp.net http azure post

我正在尝试为iOS应用程序编写一个登录功能,以window azure(ASP.net)作为服务器。登录功能应该只是模拟使用 Web 浏览器登录,并使用 NSURLSessionDataTask 编写。我正在构建 http post header 字段,但登录不成功(使用网络浏览器登录时有效),请帮忙!

HTTP post 的代码:

NSURLSession sessionLogin = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[NSHTTPCookieStorage sharedHTTPCookieStorage]setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];

NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:self.loginPageURL];
[request setHTTPMethod:@"POST"];
[request setHTTPShouldHandleCookies:YES];

NSString* postBodyString = @"";
postBodyString = [postBodyString addStringAtLast:@"__LASTFOCUS="];
postBodyString = [postBodyString addStringAtLast:lastFocus];
postBodyString = [postBodyString addStringAtLast:@"&__VIEWSTATE="];
postBodyString = [postBodyString addStringAtLast:viewState];
postBodyString = [postBodyString addStringAtLast:@"&__VIEWSTATEGENERATOR="];
postBodyString = [postBodyString addStringAtLast:viewStateGenerator];
postBodyString = [postBodyString addStringAtLast:@"&__EVENTTARGET="];
postBodyString = [postBodyString addStringAtLast:eventTarget];
postBodyString = [postBodyString addStringAtLast:@"&__EVENTARGUMENT="];
postBodyString = [postBodyString addStringAtLast:eventArgument];
postBodyString = [postBodyString addStringAtLast:@"&__EVENTVALIDATION="];
postBodyString = [postBodyString addStringAtLast:eventValidation];
postBodyString = [postBodyString addStringAtLast:@"&TxtUserName="];
postBodyString = [postBodyString addStringAtLast:userName];
postBodyString = [postBodyString addStringAtLast:@"&TxtPassword="];
postBodyString = [postBodyString addStringAtLast:password];
postBodyString = [postBodyString addStringAtLast:@"&BtnLogin="];
postBodyString = [postBodyString addStringAtLast:@"Login"];
request.HTTPBody = [postBodyString dataUsingEncoding:NSUTF8StringEncoding];

[request addValue:@"text/html, application/xhtml+xml, */*" forHTTPHeaderField:@"Accept"];
[request addValue:@"http://mywebsite.azurewebsites.net/Login" forHTTPHeaderField:@"Referer"];
[request addValue:@"en-US" forHTTPHeaderField:@"Accept-Language"];
[request addValue:@"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" forHTTPHeaderField:@"User-Agent"];
[request addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"gzip, deflate" forHTTPHeaderField:@"Accept-Encoding"];
[request addValue:@"1" forHTTPHeaderField:@"DNT"];
[request addValue:@"no-cache" forHTTPHeaderField:@"Pragma"];

NSURLSessionDataTask* taskLoginPost = [sessionLogin dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (error) {
            [self internalLoginFail];
            return;
        }
        NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
        if (httpResponse.statusCode == 200) {
            if ([self checkIsLoggedIn]) {
                [self internalLoginSuccessfully];
            }else{
                [self internalLoginFail];
            }
        }else{
            [self internalLoginFail];
            return;
        }
    });
}];
[taskLoginPost setTaskDescription:@"loginPost"];

(我检查了 HTTP 响应,现在是相同的登录页面,而不是欢迎页面。) 根据 HTTP GET 中的数字检查 POST 正文。 POST 正文字符串为:

__LASTFOCUS=&__VIEWSTATE=tTO1jEyw9uujQetfafcpid2ez1LpCrcDjxNjoc%2FDYdfONzPQmnpKPHg6%2FlovCahp29g8SVlDv3XZE2DlP4oh%2B3DUFykzGYAue57wx4xQaoc%3D&__VIEWSTATEGENERATOR=C2EE9ABB&__EVENTTARGET=&__EVENTARGUMENT=&__EVENTVALIDATION=&TxtUserName=userName&TxtPassword=password&BtnLogin=Login

我还在 Windows 计算机(Win 7 Professional SP1,以 Internet Explorer 作为浏览器)上使用 Fiddler 来测试 Web 浏览器将创建什么 http header 。

Fiddler 原始 header :

POST http://mywebsite.azurewebsites.net/Login HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://mywebsite.azurewebsites.net/Login
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Content-Length: 451
DNT: 1
Host: mywebsite.azurewebsites.net
Pragma: no-cache
Cookie: ai_user=p7U3K|2016-11-21T14:00:39.756Z

__LASTFOCUS=&__VIEWSTATE=1U4M1FGvLdr6NMFUo3gwbA6Wpy8jeIk9wMsJuVU19H8ajFWrmvTJ5NJH0UDxyj5NGnoLvo%2BjD16ZKIyhsfiuDPeJj8%2BR76LHzU11E6dcU2s%3D&__VIEWSTATEGENERATOR=C2EE9ABB&__EVENTTARGET=&__EVENTARGUMENT=&__EVENTVALIDATION=bKj4dEUxyrlZvgd61mqLjXawMxG%2BZbwLIKZ3OmWY5nUPrJbPJGqWdxYKJ5XcIV8M6lRHZFYYEgpaFSE0m26sB51Iy6OX18u%2FNU8AtB%2B1mEzhb66CLQdSaB9Wh7S6ONYGH1H4TN5aqrMdVcZLuM4t28qYPmgK9PU7PsZsDwQN5xM%3D&TxtUserName=userName&TxtPassword=password&BtnLogin=Login

我知道iOS文档要求我们不要触摸“连接”和“主机”字段,因此这些字段没有设置。我猜测还有其他 header 字段不应被触及或未包含 cookie,请告知!

<小时/>

有关该问题的更多信息:

我通过解析同一登录页面的 HTTP GET 响应来获取 View 状态和事件验证。

使用以下代码对 View 状态参数进行百分比处理:

string = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]];

HTTP GET (200) 的响应是:

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>

</title></head>
<body>
    <form method="post" action="./Login" id="form1">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="hNYEj6Hwo6IQMJtvd/Cleu1GVE+R1swakoSqxOw/MzD9t9rcFA6FeJLXnJtiN0mps4R63bXdDiay1azmtWOOyw+9GUFtuzPsgGeR7OJr1N0=" />

<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="C2EE9ABB" />
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="IIaKIcjReI3FRpw+340hMXDTtqp5S3Dm6u2hOu0pzvIGfrTGhmw8GMhTPXHjDjQbyhfDgquuwmyCAhtwIva5ceLJbT2bjcKEv2xSa8aZZBgoqqpyzXyAfo2ltj1vek58JHnpi1y73Tm4bGIcimpnQaHaODGdgtrVqpsxAjaEpEA=" />
    <div>
        <table>
            <tr>
                <td colspan="2"><h3>Login</h3></td>
            </tr>
            <tr>
                <td style="width:200px">User Name</td>
                <td style="width:200px">
                    <input name="TxtUserName" type="text" id="TxtUserName" /></td>
            </tr>
            <tr>
                <td style="width:200px">Password</td>
                <td style="width:200px">
                    <input name="TxtPassword" type="password" id="TxtPassword" /></td>
            </tr>
            <tr>
                <td style="width:200px"></td>
                <td style="width:200px">
                    <input type="submit" name="BtnLogin" value="Login" id="BtnLogin" /></td>
            </tr>
            <tr>
                <td colspan="2">
                    <span id="LblStatus"><font color="Red"></font></span>
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

相应的HTTP POST正文(与之前提供的类似,但具有相应的 View 状态参数):

__LASTFOCUS=&__VIEWSTATE=hNYEj6Hwo6IQMJtvd%2FCleu1GVE%2BR1swakoSqxOw%2FMzD9t9rcFA6FeJLXnJtiN0mps4R63bXdDiay1azmtWOOyw%2B9GUFtuzPsgGeR7OJr1N0%3D&__VIEWSTATEGENERATOR=C2EE9ABB&__EVENTTARGET=&__EVENTARGUMENT=&__EVENTVALIDATION=IIaKIcjReI3FRpw%2B340hMXDTtqp5S3Dm6u2hOu0pzvIGfrTGhmw8GMhTPXHjDjQbyhfDgquuwmyCAhtwIva5ceLJbT2bjcKEv2xSa8aZZBgoqqpyzXyAfo2ltj1vek58JHnpi1y73Tm4bGIcimpnQaHaODGdgtrVqpsxAjaEpEA%3D&TxtUserName=userName&TxtPassword=password&BtnLogin=Login

此 HTTP POST 的响应如下,与代码 200 相同的登录页面:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>

</title></head>
<body>
   <form method="post" action="./Login" id="form1">
<div class="aspNetHidden">
<input type="hidden" name="__LASTFOCUS" id="__LASTFOCUS" value="" />
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="7wQhosmVuB91RmJm9CZqtGey6rkGTzwh/ytPaVUOTsf+sxAeoKT3zwOGlrRAhF3H8YbkkfLe/LurWRYVmp7FzGPxPKqD2RIsnaeYMnERyHc=" />
</div>

<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['form1'];
if (!theForm) {
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
    theForm.__EVENTTARGET.value = eventTarget;
    theForm.__EVENTARGUMENT.value = eventArgument;
    theForm.submit();
    }
}
//]]>
</script>


<script src="/WebResource.axd?d=pynGkmcFUV13He1Qd6_TZNQUUtepuM0sahBVKa4djcXHJNs4IjHfPCRkdu-LUQJuvNtmNaMRh_LgkWrVaBEbeg2&amp;t=636093180385155047" type="text/javascript">    </script>


<script src="/WebResource.axd?d=JoBkLzP19aTuxbWOhHobYgm0nosK7P6rQ0lvYYlP0EItV4UWoFwUdFkkH6_2lw2qRb93mcvXAyCwdGo5anHBlg2&amp;t=636093180385155047" type="text/javascript"></script>
<div class="aspNetHidden">

    <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="C2EE9ABB" />
    <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
    <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="HIvhdxMyttpa9nawg7H4VJUId1YEHo4FabT5ymU/GsO84ybDeq68xGUHGlQ43wRdWpqGDu2Tdpvv5bMkzNi03EgI/okDmhMGnNsaQYbT6ARwglFDZTlcGR1B+Zc/pmK9HVA9J1GCSgqYPhvNHgRFyZb/weM/AQskcKAYCa2TX4E=" />
</div>
    <div>
        <table>
            <tr>
                <td colspan="2"><h3>Login</h3></td>
            </tr>
            <tr>
                <td style="width:200px">User Name</td>
                <td style="width:200px">
                    <input name="TxtUserName" type="text" id="TxtUserName" style="width:200px;" /></td>
            </tr>
            <tr>
                <td style="width:200px">Password</td>
                <td style="width:200px">
                    <input name="TxtPassword" type="password" id="TxtPassword" style="width:200px;" /></td>
            </tr>
            <tr>
                <td style="width:200px"></td>
                <td style="width:200px">
                    <input type="submit" name="BtnLogin" value="Login" id="BtnLogin" style="width:200px;" /></td>
            </tr>
            <tr>
                <td colspan="2">
                    <span id="LblStatus" style="color:Red;"></span>
                </td>
            </tr>
        </table>
    </div>


<script type="text/javascript">
//<![CDATA[
WebForm_AutoFocus('TxtUserName');//]]>
</script>
</form>
</body>
</html>
<小时/>

收到评论后,我使用以下代码创建了一个新项目:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* urlString = @"http://mywebsite.azurewebsites.net/Login.aspx";
    NSURL* url = [NSURL URLWithString:urlString];
    NSString* userid = @"userName";
    NSString* password = @"password";
    [self retrieveLoginFieldsFromURL:url completionHandler:^(NSDictionary *parameters) {
        [self loginWithURL:url user:userid password:password parameters:parameters completionHandler:^(BOOL success) {
            if (success) {
                NSLog(@"success");
            } else {
                NSLog(@"failure");
            }
        }];
    }];
}
- (void)retrieveLoginFieldsFromURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *parameters))completionHandler {
    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data == nil || error != nil) {
            NSLog(@"%@", error);
            return;
        }

        TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data];

        NSArray *hidden = [doc searchWithXPathQuery:@"//input[@type='hidden']"];
        NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
        for (TFHppleElement *element in hidden) {
            NSString *key = element[@"id"];
            NSString *value = element[@"value"];
            if (key) { parameters[key] = value ?: @""; }
        }
        completionHandler(parameters);
    }];
    [task resume];
}

- (void)loginWithURL:(NSURL *)url user:(NSString *)user password:(NSString *)password parameters:(NSDictionary *)parameters completionHandler:(void (^)(BOOL))completionHandler {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPMethod:@"POST"];

    NSMutableDictionary *fullParameters = [parameters mutableCopy];
    fullParameters[@"TxtUserName"] = user;
    fullParameters[@"TxtPassword"] = password;
    fullParameters[@"BtnLogin"] = @"Login";

    [request setHTTPBody:[self httpBodyForParameters:fullParameters]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data == nil || error != nil) {
            NSLog(@"%@", error);
            return;
        }

        BOOL success = ![response.URL.path isEqualToString:@"/Login"];

        completionHandler(success);
    }];
    [task resume];
}
- (NSData *)httpBodyForParameters:(NSDictionary *)parameters {
    NSMutableArray *parameterArray = [NSMutableArray array];

    [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
        NSString *param = [NSString stringWithFormat:@"%@=%@", [self percentEscapeString:key], [self percentEscapeString:obj]];
        [parameterArray addObject:param];
    }];

    NSString *string = [parameterArray componentsJoinedByString:@"&"];

    return [string dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSString *)percentEscapeString:(NSString *)string {
    NSCharacterSet *allowed = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"];
    return [string stringByAddingPercentEncodingWithAllowedCharacters:allowed];
}

我在这个新项目中更改的唯一设置是将 plist 中的以下项目更改为 true: NSAppTransportSecurity>NSAllowsArbitraryLoads

我现在怀疑我的应用程序的设置存在差异。 请指教。

<小时/>

感谢 Rob 提供的众多建议!

检测到无法对网站执行 HTTP POST 的原因是 NSURL 中的扩展名,即在我的情况下没有“.aspx”。

最佳答案

在您最初的问题中,您询问需要哪些特殊 header 。不需要特殊的 header (Content-Type 除外)。

在修改后的问题中,您声明不应在 NSURL 中添加扩展程序。严格来说这并不正确。是否包含扩展名与 NSURL 无关,而只是 Web 服务器上的资源调用什么的问题。有时服务器需要扩展名(在这种情况下,NSURL 必须包含它),但在这种情况下,ASP.NET 操作不使用扩展名,因此您不会在此使用扩展名具体情况。

话虽如此,有一些观察结果:

  1. 您需要使用 GET 请求检索登录页面,解析 HTML 以获取隐藏字段的内容。我建议使用 TFHpple

  2. 然后,您需要将这些字段与用户 ID 和密码字段组合起来,然后提交 POST 请求。

  3. 构建请求时,不要忘记对值进行百分比转义,以确保您的请求格式正确。

  4. 你说它“失败”。您应该编辑问题以包含错误和 URLResponse 值。

无论如何,我尝试了以下操作并成功登录:

[self retrieveLoginFieldsFromURL:url completionHandler:^(NSDictionary *parameters) {
    [self loginWithURL:url user:userid password:password parameters:parameters completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"success");
        } else {
            NSLog(@"failure");
        }
    }];
}];

哪里

- (void)retrieveLoginFieldsFromURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *parameters))completionHandler {
    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data == nil || error != nil) {
            NSLog(@"%@", error);
            return;
        }

        TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data];

        NSArray *hidden = [doc searchWithXPathQuery:@"//input[@type='hidden']"];
        NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
        for (TFHppleElement *element in hidden) {
            NSString *key = element[@"id"];
            NSString *value = element[@"value"];
            if (key) { parameters[key] = value ?: @""; }
        }
        completionHandler(parameters);
    }];
    [task resume];
}

- (void)loginWithURL:(NSURL *)url user:(NSString *)user password:(NSString *)password parameters:(NSDictionary *)parameters completionHandler:(void (^)(BOOL))completionHandler {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPMethod:@"POST"];

    NSMutableDictionary *fullParameters = [parameters mutableCopy];
    fullParameters[@"TxtUserName"] = user;
    fullParameters[@"TxtPassword"] = password;
    fullParameters[@"BtnLogin"] = @"Login";

    [request setHTTPBody:[self httpBodyForParameters:fullParameters]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data == nil || error != nil) {
            NSLog(@"%@", error);
            return;
        }

        // we can determine success from whether we're still at login page or not; if so, we failed; if not, we succeeded

        BOOL success = ![response.URL.path isEqualToString:@"/Login"];

        completionHandler(success);
    }];
    [task resume];
}

/** Build the body of a `application/x-www-form-urlencoded` request from a dictionary of keys and string values

 @param parameters The dictionary of parameters.
 @return The `application/x-www-form-urlencoded` body of the form `key1=value1&key2=value2`
 */
- (NSData *)httpBodyForParameters:(NSDictionary *)parameters {
    NSMutableArray *parameterArray = [NSMutableArray array];

    [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
        NSString *param = [NSString stringWithFormat:@"%@=%@", [self percentEscapeString:key], [self percentEscapeString:obj]];
        [parameterArray addObject:param];
    }];

    NSString *string = [parameterArray componentsJoinedByString:@"&"];

    return [string dataUsingEncoding:NSUTF8StringEncoding];
}

/** Percent escapes values to be added to a URL query as specified in RFC 3986.

 See http://www.ietf.org/rfc/rfc3986.txt

 @param string The string to be escaped.
 @return The escaped string.
 */
- (NSString *)percentEscapeString:(NSString *)string {
    NSCharacterSet *allowed = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"];
    return [string stringByAddingPercentEncodingWithAllowedCharacters:allowed];
}

关于iOS 不应在 HTTP POST 中的 NSURLSession 的 NSURL 中添加扩展名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40728386/

相关文章:

java - ReSTLet 路由器相对路径错误

ios - 在不使用自动布局的情况下调整 iOS 11 中的条形按钮项目

iphone - 为 UITableView 的第一个和最后一个单元格制作圆角

c# - 在特定时间清空表并且仅一次(每天)

c# - 将 IEnumerable<string> 转换为 IEnumerable<ListItem>

asp.net - 如何设置 IHttpAsyncHandler 超时?

ios - 快速访问类文件时出现上下文闭包类型错误

ios - 从 .txt 文件读取/写入 iOS 7 objc

http - 如何使用服务器端事件处理错误和状态代码

http - IceCast 2.3.2-kh29服务器流404错误