iOS:使用XMLHttpRequest进行身份validation – 处理401响应

我正在使用PhoneGap(又名Cordova)编写iOS应用程序,我有一个简单的html登录页面,它使用XMLHttpRequest和SSL上的基本身份validation来记录用户。 当您正确输入用户名和密码时,一切都很顺利。 但是,如果输入错误的用户名/密码,则不会调用任何回调。

例如,如果您在Chrome上运行相同的代码,使用错误的用户名/密码,则Chrome会以类似的方式运行,但会弹出身份validation质询对话框。 在chrome的对话框上点击取消会将控制权返回给我的javascript代码。 不幸的是,在iOS上,UIWebView甚至不会弹出一个auth对话框,它只是挂起。 我需要一种方法告诉用户他们输入了错误的用户名或密码,以便他们可以重试。

我能找到的答案最接近的是http://www.freelock.com/2008/06/technical-note-http-auth-with-ajax但是从服务器更改响应状态似乎不是正确的事情。

这基本上是我的请求代码的样子,但是当发送一个错误的用户名或密码时,它永远不会到达我的onload回调(实际上onreadystatechange回调只被调用一次,那就是readyState 1,也就是OPEN)。

var req = new XMLHttpRequest(); req.onload = function(ev) { if (req.status == 401) { alert("Invalid Username/Password"); document.getElementById('password').focus(); } else if (req.status == 200) { window.location.href = some_secure_site; } else { // edit // alert("Some other status"); } } req.onerror = function (ev) { alert('Error'); }; req.ontimeout = function(ev) { alert('Timeout'); }; req.open('GET', uri, true, userValue, passValue); req.withCredentials = true; req.send(); 

在尝试在iOS上执行此操作时,我发现了一些事情。 一个是iOS有一个与基本身份validation相关的错误,因此如果您的密码中包含某些特殊字符,您将永远不会从服务器获得响应,因为您的服务器永远不会遇到身份validation质询。 也就是说,如果您在“打开”方法中使用用户名和密码字段。

我的猜测是他们正在做一些愚蠢的事情,比如通过http:// username:password@myorigin.com/etc发送它们应该使用http头和base64编码这样的信用卡是这样的

 req.setRequestHeader("Authorization", "Basic " + base64(username) + ':' + base64(password)); 

我学到的另一件事是Basic Auth不是非常安全,容易出现一百万个问题。 其中一个会让你恼火的是客户端将缓存用户名和密码,这将覆盖你通过“req.open(…)”发送的任何新值。 祝你好运,只使用javascript,你必须在ObjC中做一些神奇的事情来清除缓存。

如果您可以控制服务器,我建议使用令牌身份validation。 通过SSL连接,然后发送包含用户名和密码的JSON数据的POST。 然后,服务器可以使用身份validation令牌发送回JSON数据(基本上是一堆随机字符足够长,以至于无法猜到,UUID运行良好。这是由服务器生成的,只能由客户端知道并且服务器)。 然后将令牌和用户名存储在钥匙串中,这样用户每次启动应用程序时都不需要输入他们的信用卡。

我的服务器将始终发回200响应,但JSON数据将包含重试或存储身份validation令牌所需的信息。 一般来说……基本认证基本上很糟糕。

 try { var req = new XMLHttpRequest(); req.onload = function(ev) { var response = JSON.parse(this.responseText); if (response.success === true) { // The server will respond with a token that will allow us to login storeCredentials(userValue, response.token); // redirect with token else if (req.status == 401) { alert("Invalid Username/Password"); document.getElementById('password').focus(); } else { alert("Some other status"); } } req.ontimeout = setTimeout(function(ev) { navigator.notification.alert('Timeout trying to contact the server'); }, 10000); req.onerror = function(ev) { clearTimeout(this.ontimeout); navigator.notification.alert('Error connecting to the server during authentication.'); }; var uri = myWebOrigin + '/authenticate'; req.open('POST', uri, true); req.setRequestHeader('Cache-Control', 'no-cache'); req.setRequestHeader('Content-Type', 'application/json'); json_data = {username : Base64.encode(userValue), password : Base64.encode(passValue)}; req.send(JSON.stringify(json_data)); } catch(error) { navigator.notification.alert('Uh oh, an error occurred trying to login! ' + error); return; } 

我在使用iOS + PhoneGap + jQuery时没有调用任何回调时遇到了同样的问题。 如果我传递错误的凭据并使用

 $.ajax({ ... timeout: 5000, // Some timeout value that makes sense ... }); 

然后使用{"readyState":0,"status":0,"statusText":"timeout"}调用错误回调{"readyState":0,"status":0,"statusText":"timeout"} 。 在这种情况下,你必须猜测真正的错误是HTTP 401。

或者你可以使用

 $.ajax({ ... async: false, // :-( ... }); 

并且您的错误回调将获得类似{"readyState":4,"responseText":"...","status":401,"statusText":"Unauthorized"}返回。

收到的401200代码旁边可能还有其他HTTP状态代码! 确保没有收到其他状态代码:

 if (req.status == 401) { alert("Invalid Username/Password"); document.getElementById('password').focus(); } else if (req.status == 200) { window.location.href = some_secure_site; } else { alert('Unfetched status code '+req.status+' captured!'); } 

我一直有同样的问题 – 如果传入无效凭据,则不会调用请求中的任何回调。

我的猜测是在内部UIWebView被告知提示输入凭据,但该提示被抑制,导致此错误。 这是基于我尝试过的所有其他浏览器(Safari,Chrome,Firefox)在这种情况下都没有调用回调,直到提示被取消。

另一个解决方案(我正在使用的)是不使用Javascript进行身份validation,而是在iOS端执行 – 您可以使用如何在UIWebView中显示身份validation挑战的代码? 作为这样做的粗略模板。

要解决此问题,请从服务器响应中删除标题WWW-Authenticate

Interesting Posts