我正在尝试为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&t=636093180385155047" type="text/javascript"> </script>
<script src="/WebResource.axd?d=JoBkLzP19aTuxbWOhHobYgm0nosK7P6rQ0lvYYlP0EItV4UWoFwUdFkkH6_2lw2qRb93mcvXAyCwdGo5anHBlg2&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 操作不使用扩展名,因此您不会在此使用扩展名具体情况。
话虽如此,有一些观察结果:
您需要使用
GET
请求检索登录页面,解析 HTML 以获取隐藏字段的内容。我建议使用 TFHpple 。然后,您需要将这些字段与用户 ID 和密码字段组合起来,然后提交
POST
请求。构建请求时,不要忘记对值进行百分比转义,以确保您的请求格式正确。
你说它“失败”。您应该编辑问题以包含错误和
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/