How should AngularJS handle 403 error in $http.post due to outdated XSRF token?

时间:2016-02-12 20:51:47

标签: javascript angularjs

An AngularJS version 1.4.8 app is getting an unhandled // 24 = 192 bits private const int SaltByteSize = 24; private const int HashByteSize = 24; private const int HasingIterationsCount = 10101; public static string HashPassword(string password) { // http://stackoverflow.com/questions/19957176/asp-net-identity-password-hashing byte[] salt; byte[] buffer2; if (password == null) { throw new ArgumentNullException("password"); } using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, SaltByteSize, HasingIterationsCount)) { salt = bytes.Salt; buffer2 = bytes.GetBytes(HashByteSize); } byte[] dst = new byte[(SaltByteSize + HashByteSize) + 1]; Buffer.BlockCopy(salt, 0, dst, 1, SaltByteSize); Buffer.BlockCopy(buffer2, 0, dst, SaltByteSize + 1, HashByteSize); return Convert.ToBase64String(dst); } public static bool VerifyHashedPassword(string hashedPassword, string password) { byte[] _passwordHashBytes; int _arrayLen = (SaltByteSize + HashByteSize) + 1; if (hashedPassword == null) { return false; } if (password == null) { throw new ArgumentNullException("password"); } byte[] src = Convert.FromBase64String(hashedPassword); if ((src.Length != _arrayLen) || (src[0] != 0)) { return false; } byte[] _currentSaltBytes = new byte[SaltByteSize]; Buffer.BlockCopy(src, 1, _currentSaltBytes, 0, SaltByteSize); byte[] _currentHashBytes = new byte[HashByteSize]; Buffer.BlockCopy(src, SaltByteSize + 1, _currentHashBytes, 0, HashByteSize); using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, _currentSaltBytes, HasingIterationsCount)) { _passwordHashBytes = bytes.GetBytes(SaltByteSize); } return AreHashesEqual(_currentHashBytes, _passwordHashBytes); } private static bool AreHashesEqual(byte[] firstHash, byte[] secondHash) { int _minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length; var xor = firstHash.Length ^ secondHash.Length; for (int i = 0; i < _minHashLength; i++) xor |= firstHash[i] ^ secondHash[i]; return 0 == xor; } error when its login form sends data to a backend REST authentication service after the user's browser has been left open for many (16 in this case) hours. Upon deeper analysis, the root cause is that the client AngularJS app has outdated cookies for 403 and XSRF-TOKEN, which causes the backend Spring Security to reject the request to the public JSESSIONID service because Spring thinks the request is cross site request forgery.

The problem can be resolved manually if the user closes all browser windows and then re-opens a new browser window before making the request again. But this is not an acceptable user experience. I have read the AngularJS documentation at this link, and I see that I can add an /login1 function, but how specifically should i re-write the function to handle the errorCallback error?

Here is the original 403 method in the authorization service, which you can see does not handle this.logForm() errors:

403

Here is my very rough attempt at a revised version of the this.logForm = function(isValid) { if (isValid) { var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password }; $http.post('/login1', usercredentials) .then( function(response, $cookies) { if(response.data.content=='login1success'){// do some stuff } else {// do other stuff } } ); } }; method attempting to handle a this.logForm() error following the example in the AngularJS documentation:

403

What specific changes need to be made to the code above to handle the 403 error due to server-perceived XSRF-TOKEN and JSESSIONID issues? And how can the post be called a second time after deleting the cookies without leading to an infinite loop in the case where deleting the cookies does not resolve the 403 error?

I am also looking into global approaches to error handling, but there is a combination of public and secure backend REST services, which would need to be handled separately, leading to complexity. This login form is the first point of user entry, and I want to handle it separately before looking at global approaches which would retain a separate handling of the login form using methods developed in reply to this OP.

2 个答案:

答案 0 :(得分:0)

You could restructure your http calls to auto retry, and use promises in your controllers (or whatever)

const routes = {
    childRoutes: [
        { path: '/', component: Home },
        { path: '/login', component: Login },
        { path: '/register', component: Register, childRoutes: [
            { path: 'step2', component: SecondStep },
        ] },
    ]
};

and then you would call

  var httpPostRetry = function(url, usercredentials) {
    var promise = new Promise(function(resolve, reject) {

    var retries = 0;
    var postRetry = function(url, usercredentials) {
      if (retries < 3) {
       $http({ method: 'POST', url: '/login1', usercredentials })
        .then(function(result) {
          resolve(result);
        }).catch(function(result) {
          retries ++;
          postRetry(url, usercredentials);
        });
      } else {
        reject(result);
      }
    };

  }.bind(this));
  return promise;
}

To handle specific http errors you can broadcast that specific error and handle that case in a specific controller. Or use a service to encapsulate the status and have some other part of your code handle the UI flow for that error.

httpPostRetry(bla, bla).then(function(result) {
  // one of the 3 tries must of succeeded
}).catch(function(result) {
  // tried 3 times and failed each time
});

Does this help?

答案 1 :(得分:-1)

Have a look at the angular-http-auth module and how things are done there. I think one key element you would want to use is a http interceptor.

For purposes of global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses, it is desirable to be able to intercept requests before they are handed to the server and responses before they are handed over to the application code that initiated these requests. The interceptors leverage the promise APIs to fulfill this need for both synchronous and asynchronous pre-processing.

After playing around with interceptors you can look at the angular-http-auth http buffer and the way they handle rejected requests there. If their interceptor receives a delete, they add the config object - which basically stores all information about your request - to a buffer, and then any time they want they can manipulate elements in that buffer. You could easily adept their code to manipulate the config's xsrfHeaderName, xsrfCookieName, or parameters on your behalf when you receive a 403.

I hope that helps a little.