javascript iOS:使用 XMLHttpRequest 进行身份验证 - 处理 401 响应

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11025213/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 11:51:13  来源:igfitidea点击:

iOS: Authentication using XMLHttpRequest - Handling 401 response

javascriptioscordovaxmlhttprequestbasic-authentication

提问by Matthew Levine

I'm writing an iOS application using PhoneGap (aka Cordova), I have a simple html login page that logs the user in using an XMLHttpRequest with basic authentication over SSL. Everything works splendidly when you enter your username and password correctly. However, if you enter the wrong username/password none of my callbacks are ever called.

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

If you run the same code on Chrome for example, with the wrong username/password, chrome behaves in a similar manner, except it pops up an authentication challenge dialog. Hitting cancel on chrome's dialog returns control to my javascript code. Unfortunately, on iOS, the UIWebView wont even popup an auth dialog, it just hangs. I need a way to tell the user that they entered the wrong username or password so they can retry.

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

The closest thing to an answer I could find was this http://www.freelock.com/2008/06/technical-note-http-auth-with-ajaxbut changing the response status from the server doesn't seem like the right thing to do.

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

Here's basically what my request code looks like, but when a bad username or password is sent it never reaches my onload callback (in fact the onreadystatechange callback only gets called once and thats for readyState 1, aka OPEN).

这基本上是我的请求代码的样子,但是当发送错误的用户名或密码时,它永远不会到达我的 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();

采纳答案by Matthew Levine

A few things became apparent to me while trying to do this on iOS. One is that iOS has a bug relating to basic auth, so if your password has certain special characters in it you'll never get a response back from your server because your server will never get an authentication challenge. That is, if you're using the username and password field in the "open" method.

尝试在 iOS 上执行此操作时,有几件事对我来说变得很明显。一个是 iOS 存在与基本身份验证相关的错误,因此如果您的密码中包含某些特殊字符,您将永远不会从服务器收到响应,因为您的服务器永远不会收到身份验证挑战。也就是说,如果您在“打开”方法中使用用户名和密码字段。

My guess is they are doing something stupid like sending it via http://username:[email protected]/etcwhen they should be using http headers and base64 encoding the creds like so

我的猜测是他们正在做一些愚蠢的事情,比如通过http://username:[email protected]/etc发送它时他们应该使用 http 标头和 base64 编码像这样的信用

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

The other thing I learned is that Basic Auth isnt very secure and is prone to a million and one problems. One of which that will annoy you is that the client will cache the username and password, which will override any new values you send via "req.open(...)". Good luck getting around that using javascript alone, you'll have to do some magic in ObjC to clear the cache.

我学到的另一件事是 Basic Auth 不是很安全,并且容易出现一百万零一个问题。其中一个会让您烦恼的是客户端将缓存用户名和密码,这将覆盖您通过“req.open(...)”发送的任何新值。祝你好运,单独使用 javascript 解决这个问题,你必须在 ObjC 中做一些魔术来清除缓存。

If you have control over your server, I would suggest using token authentication. Connect over SSL and then send a POST with JSON data containing the username and password. The server could then send back JSON data with an authentication token (essentially a bunch of random characters long enough that it can't ever be guessed, a UUID works well. this is generated by the server and can only be known to the client and the server). Then store the token and the username in the keychain so the user doesnt need to enter their creds everytime they start your app.

如果您可以控制服务器,我建议您使用令牌身份验证。通过 SSL 连接,然后使用包含用户名和密码的 JSON 数据发送 POST。然后服务器可以使用身份验证令牌发回 JSON 数据(本质上是一堆足够长的随机字符,它永远不会被猜到,UUID 工作得很好。这是由服务器生成的,只能被客户端知道和服务器)。然后将令牌和用户名存储在钥匙串中,这样用户每次启动您的应用程序时都不需要输入他们的凭据。

My server will always send back a 200 response but the JSON data will contain the information needed to either retry or to store the auth token. In general... basic auth basically sucks.

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

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;
}

回答by Roy Solberg

I just had the same issues with none of the callbacks being called when using iOS + PhoneGap + jQuery. If I pass incorrect credentials and use

我只是遇到了同样的问题,在使用 iOS + PhoneGap + jQuery时没有调用任何回调。如果我传递不正确的凭据并使用

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

then the error callback is called with {"readyState":0,"status":0,"statusText":"timeout"}. In that case you would have to guess that the real error is the HTTP 401.

然后错误回调被调用{"readyState":0,"status":0,"statusText":"timeout"}。在这种情况下,您将不得不猜测真正的错误是 HTTP 401。

Alternatively you can use

或者你可以使用

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

and your error callback will get something like {"readyState":4,"responseText":"<html>...</html>","status":401,"statusText":"Unauthorized"}back.

并且您的错误回调将{"readyState":4,"responseText":"<html>...</html>","status":401,"statusText":"Unauthorized"}返回类似的内容。

回答by Bruno Peres

To solved this problem remove the header WWW-Authenticatefrom server response.

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

回答by powtac

There is probably an other HTTP status code beside the 401and 200codes received! Make sure there is really no other status code received:

除了收到的401和代码之外,可能还有其他 HTTP 状态代码200!确保确实没有收到其他状态代码:

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!');
}

回答by Krease

I've been having the same issue - none of the callbacks on the request are being called if invalid credentials are passed in.

我遇到了同样的问题 - 如果传入的凭据无效,则不会调用请求中的任何回调。

My guess is that internally the UIWebView is being told to prompt for credentials, but that prompt is being suppressed, leading to this bug. This is based on all the other browsers I've tried (Safari, Chrome, Firefox) not calling the callbacks in this case until after the prompt is dismissed.

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

Another solution (the one I'm using) would be to not use Javascript to do the authentication, and instead do it on the iOS side - you can use the code on How to display the Authentication Challenge in UIWebView?as a rough template for doing this.

另一种解决方案(我正在使用的解决方案)是不使用 Javascript 进行身份验证,而是在 iOS 端进行 - 您可以使用有关如何在 UIWebView 中显示身份验证质询的代码作为执行此操作的粗略模板。