身份验证 - 调用 $http 的 .then() 成功回调而不是错误回调

Posted

技术标签:

【中文标题】身份验证 - 调用 $http 的 .then() 成功回调而不是错误回调【英文标题】:Authentication - $http's .then() success callback called instead of error callback 【发布时间】:2016-03-20 12:27:07 【问题描述】:

这可能只是来自对如何在 MEAN 堆栈应用程序中进行最佳身份验证的误解,或者我对 Promise 和 $http 的 .then() 方法如何工作缺乏了解,但每当我尝试对我的后端节点服务器进行身份验证时如果凭据不正确,它会调用 $http 的 .then() 方法的成功回调而不是错误回调。这是我的设置:

我正在使用 jsonwebtokenexpress-jwt 包,AngularJS 拦截器添加令牌以请求并检查状态 401 responseErrors,设置/删除等 JWT 的 TokenService,以及处理登录的 UserService,注销等

从调试来看,发生了什么:

    发送登录请求 服务器捕获请求,查找指定用户,但在数据库中找不到它们。使用 JSON 对象返回 401 错误,包括错误消息等。 HttpInterceptor 使用 responseError 方法,正确地看到它是一个状态 401,删除任何可能的现有令牌,重定向到 /login 屏幕和 returns $q.reject(response)UserService.login() 正确使用错误回调并执行return response问题 - 我的 login.js .login() 方法中的成功回调运行,而不是第二个错误回调。我感觉这与this article about promise chaining 中讨论的内容有关,但我的专业知识在这里有其局限性,我不太明白接下来应该做什么来告诉链中的下一个回调前一个回调出错了...

这是我的设置:

快递:

authRoutes.js

authRoutes.post("/login", function (req, res) 

    User.findOne(username: req.body.username, function (err, user) 
        if (err) res.status(500).send(err);
        if (!user) 
            res.status(401).send(success: false, message: "User with the provided username was not found")
         else if (user) 
            bcrypt.compare(req.body.password, user.password, function (err, match) 
                if (err) throw (err);
                if (!match) res.status(401).json(success: false, message: "Incorrect password");
                else 
                    var token = jwt.sign(user, config.secret, expiresIn: "24h");
                    res.json(token: token, success: true, message: "Here's your token!")
                
            );
        
    );
);

从调试中,当我使用错误的凭据登录时,它正确地点击了res.status(401).send(...) 行,所以这部分似乎没问题。

角度:

app.js(包括 HttpInterceptor)

var app = angular.module("TodoApp", ["ngRoute"]);

app.factory("AuthInterceptor", ["$q", "$location", "TokenService", function ($q, $location, TokenService) 
    return 
        request: function (config) 
            var token = TokenService.getToken();
            if (token) 
                config.headers = config.headers || ;
                config.headers.Authorization = "Bearer " + token
            
            return config;
        ,
        responseError: function (response) 
            if (response.status === 401) 
                TokenService.removeToken();
                $location.path("/login");
            
            return $q.reject(response);
        
    
]);

app.config(function ($routeProvider, $httpProvider) 
    $httpProvider.interceptors.push('AuthInterceptor');

    $routeProvider
        .when("/", 
            templateUrl: "landing/landing-page.html"
        );
);

userService.js

var app = angular.module("TodoApp");

app.service("UserService", ["$http", "TokenService", function ($http, TokenService) 

    this.signup = function (user) 
        return $http.post("http://localhost:8080/auth/signup", user).then(function (response) 
            return response;
        , function (response) 
            return response;
        );
    ;

    this.login = function (user) 
        return $http.post("http://localhost:8080/auth/login", user).then(function (response) 
            if (response.data.success) TokenService.setToken(response.data.token);
            return response;
        , function (response) 
            return response;
        )
    ;

    this.isAdmin = function (user) 
        return user.admin;
    ;
]);

login.js(问题似乎很明显)

var app = angular.module("TodoApp");

app.config(function ($routeProvider) 
    $routeProvider
        .when("/login", 
            templateUrl: "auth/login.html",
            controller: "LoginController"
        )
);

app.controller("LoginController", ["$scope", "$http", "$location", "UserService", "TokenService", function ($scope, $http, $location, UserService, TokenService) 

    $scope.login = function (user) 
        UserService.login(user).then(function (response) 
            $location.path("/todo");
        , function (response) 
            console.log("There was a problem: " + response);
        );
    
]);

最后一部分,UserService.login(user).then(function (response) $location.path("/todo"); 是正在运行并试图将用户重定向到待办事项列表的行,当我想要它运行 console.log("There was a problem: " + response); 行时。 .

就像我上面所说的,我感觉它与链式 Promise 以及如何在链的中途处理错误而不是通过链向下冒泡有关。不确定我是否需要像上面提到的网站那样添加.catch() 块。即使这是答案,我也不完全确定该怎么写。

如果我有更好的组织方式,我当然也愿意接受建议。我必须将这一点教给一班学生,并希望确保我在传授良好的做法。

提前感谢您的帮助!

【问题讨论】:

您正在正确处理 UserService 中的错误,因此当您访问 login.js 页面时,它会将 UserService 的任何失败视为正确处理的承诺。如果您删除 $http 请求中的错误处理部分,它将按预期工作 @tykowale 这就像一个魅力。非常感谢!我还尝试添加$q.reject(response),效果也不错,现在我对 $q 的理解更好了。 【参考方案1】:

仔细查看这部分代码:

this.login = function (user) 
    return $http.post("http://localhost:8080/auth/login", user).then(function (response) 
        if (response.data.success) TokenService.setToken(response.data.token);
        return response;
    , function (response) 
        return response;
    )

在这里,您提供了带有返回值的错误回调,该返回值被传递给 Promise 链中的下一个回调。您感到困惑的根源在于,如果您希望 error 进一步传播,您仍然需要从回调返回拒绝的 throw 承诺。否则,这实际上意味着您已经从错误情况中恢复,流程中的下一步将会成功。这就是你现在所拥有的。

在您的情况下,您可以完全删除错误回调

return $http.post("http://localhost:8080/auth/login", user).then(function (response) 
    if (response.data.success) TokenService.setToken(response.data.token);
    return response;
);

...或者确保你返回失败的承诺

return $http.post("http://localhost:8080/auth/login", user).then(function (response) 
    if (response.data.success) TokenService.setToken(response.data.token);
    return response;
, function (response) 
    return $q.reject(response);
);

...或抛出:

return $http.post("http://localhost:8080/auth/login", user).then(function (response) 
    if (response.data.success) TokenService.setToken(response.data.token);
    return response;
, function (response) 
    throw new Error(response);
);

【讨论】:

谢谢!我知道这会很容易解决,这帮助我更好地理解了 Promise 链和 $q 的工作原理!【参考方案2】:

您是否尝试在then() 调用的错误情况下使用$q.reject

例如

// remember to add $q to deps

this.login = function (user) 
    return $http.post("http://localhost:8080/auth/login", user).then(function (response) 
        if (response.data.success) TokenService.setToken(response.data.token);
        return response;
    , function (response) 
        $q.reject(response);
    )
;

相关文档:https://docs.angularjs.org/api/ng/service/$q

【讨论】:

以上是关于身份验证 - 调用 $http 的 .then() 成功回调而不是错误回调的主要内容,如果未能解决你的问题,请参考以下文章

Angular 8 在身份验证完成之前抑制 HTTP 调用

调用经过用户身份验证的 http 触发器时发生错误

通过 SSL 和 HTTP 基本身份验证的 REST 服务

如何安排经过身份验证的云功能的 http 调用?

如何使用经过身份验证的 id 令牌和数据库规则保护 Firebase Cloud Function HTTP 端点?

无法执行云函数触发不允许未经身份验证的调用的 HTTP 触发的云函数?