如何使用已有的REST API后端在Angular应用程序中实现登录系统

时间:2022-09-02 11:16:54

My friend and I are building an app - my friend is on the backend (Node.js) and I'm on the front.

我的朋友和我正在构建一个应用程序 - 我的朋友在后端(Node.js),我在前面。

He implemented sessions on his end and provided me with the URL I need to call to log in. For example, a POST request

他在他的最后实现了会话,并为我提供了我需要调用以登录的URL。例如,一个POST请求

http://ourapp.heroku.com/login

with which username and password are passed.

使用哪个用户名和密码。

On my side, in the Angular app, I create a login page which calls an Angular service when Login is clicked. If this service receives a 200 from the server, it does:

在我这边,在Angular应用程序中,我创建了一个登录页面,在单击Login时调用Angular服务。如果此服务从服务器收到200,它会:

$cookieStore.put(cookieNames.LOGGED_IN_COOKIE, true);
$state.go('home', {}, {reload: true});

The problem is that we're having weird issues with the app on the front end. For example logging in and out often don't work. Also, users are able to go to pages even after they log out. I figured out (at least I think) that I'm not properly storing the Cookie I receive from the server, I'm only storing my own.

问题是我们在前端的应用程序遇到了奇怪的问题。例如,登录和退出通常不起作用。此外,用户即使在注销后也可以访问页面。我想(至少我认为)我没有正确存储我从服务器收到的Cookie,我只存储自己的。

This whole Angular thing is still weird to me, because in PHP or Python apps you get a page request from the client and verify if he's logged in before sending him the page he requested. In Angular it's different - the user has all of the pages already. So how do I limit what he can see without logging in and how to I properly keep track of the server's cookie?

整个Angular对我来说仍然很奇怪,因为在PHP或Python应用程序中,您从客户端获得页面请求,并在向他发送他请求的页面之前验证他是否已登录。在Angular中它不同 - 用户已经拥有所有页面。那么如何在不登录的情况下限制他可以看到的内容以及如何正确跟踪服务器的cookie?

1 个解决方案

#1


2  

If you use ui-router, you can do something similar to this:

如果你使用ui-router,你可以做类似的事情:

First introduce some kind of access-levels to your states

首先向您的州介绍某种访问级别

$stateProvider
        .state('admin', {
            url: "/admin",
            templateUrl: "/app/views/admin.html",
            controller: "AdminController",
            data: {
                accessLevel: 'admin'
            }
        })

then you have to check on state change, if your logged in user has the required access-level:

如果您的登录用户具有所需的访问级别,则必须检查状态更改:

You can create an auth service which implements your logic to log your user in, as example you can use this service

您可以创建一个auth服务,该服务实现您的逻辑以记录您的用户,例如您可以使用此服务

angular.module('app')
   .factory("AuthService", ["$rootScope", "$http", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $http, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {

        function loginFailed() {
            $rootScope.$broadcast("auth-change", AUTH_EVENTS.loginFailed);
        };

        AuthSession.load();

        $rootScope.$on('$stateChangeStart', function (event, nextState) {
            if (nextState.data && nextState.data.accessLevel && !service.isAuthorized(nextState.data.accessLevel)) {
                event.preventDefault();
                $rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired, nextState.name);
            }
        });

        var service = {
            login: function (credentials) {
                return $http
                            .post('/api/account/login', credentials)
                            .success(function (data, status) {
                                if ((status < 200 || status >= 300) && data.length >= 1) {
                                    loginFailed();
                                    return;
                                }

                                AuthSession.create(data.AccessToken, data.User);
                                $rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
                                AuthHttpBuffer.retryAll();
                            }).error(function (data, status) {
                                loginFailed();
                            });
            },
            cancel: function () {
                AuthHttpBuffer.rejectAll();
            },
            logout: function () {
                AuthSession.destroy();
                $rootScope.$broadcast("auth-change", AUTH_EVENTS.logoutSuccess);
            },
            isAuthenticated: function () {
                return (AuthSession.token !== null);
            },
            isAuthorized: function (accessLevel) {
                if (!accessLevel) return true;

                return (this.isAuthenticated() && AuthSession.user.UserRoles.indexOf(accessLevel) !== -1);
            }

        }
        return service;
    }]);

and your AuthSession service:

和您的AuthSession服务:

angular.module('app')
      .factory("AuthSession", ["$rootScope", "$window", "AUTH_EVENTS", function ($rootScope, $window, AUTH_EVENTS) {

        var sessionService = {
            user: null,
            token: null,

            //load the stored session data
            load: function () {
                var user = ...yourdata... //TODO implement load user data;
                var token = ...yourdata... //implement load user data;

                if (!user || !token) return;

                if (!this.checkTokenExpiration(token)) return;

                this.user = user;
                this.token = token;

                $rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
            },

            //save the current data to the session storage
            save: function () {
                //TODO save your userdata/token etc.
            },

            //create the current user with the assosiated token
            create: function (token, user) {
                this.token = token;
                this.user = user;

                if (!angular.isArray(this.user.UserRoles))
                    this.user.UserRoles = [this.user.UserRoles];

                this.save();
            },

            //destroy an user with all assosiated data
            destroy: function () {
                this.token = null;
                this.user = null;

                //TODO clear your saved data here
            },

            //check if the supplied access token data is expired
            checkTokenExpiration: function (token) {
                if (token === undefined || token === null) return false;

                var retval = (new Date(token.TokenExpires).getTime() > new Date().getTime());

                if (retval === false) {
                    sessionService.destroy();
                    $rootScope.$broadcast("auth-change", AUTH_EVENTS.sessionTimeout);
                }

                return retval;
            }
        }

        return sessionService;

      }]);

and the constants:

和常数:

angular.module('app')
      .constant('AUTH_EVENTS', {
        loginSuccess: 'auth-login-success',
        loginFailed: 'auth-login-failed',
        logoutSuccess: 'auth-logout-success',
        loginRequired: 'auth-login-required',
        sessionTimeout: 'auth-session-timeout',
        notAuthorized: 'auth-not-authorized'
      });

If you want be able to catch urls, where you haven't the right accesrights, you can send the request to a http buffer:

如果您希望能够捕获您没有正确访问权限的URL,则可以将请求发送到http缓冲区:

angular.module('app')
      .factory('AuthHttpBuffer', ["$injector", function ($injector) {
        /** Holds all the requests, so they can be re-requested in future. */
        var buffer = [];

        /** Service initialized later because of circular dependency problem. */
        var $http;

        function retryHttpRequest(config, deferred) {
            function successCallback(response) {
                deferred.resolve(response);
            }
            function errorCallback(response) {
                deferred.reject(response);
            }
            $http = $http || $injector.get('$http');
            $http(config).then(successCallback, errorCallback);
        }

        return {
            /**
                 * Appends HTTP request configuration object with deferred response attached to buffer.
                 */
            append: function (config, deferred) {
                buffer.push({
                    config: config,
                    deferred: deferred
                });
            },

            /**
                 * Abandon or reject (if reason provided) all the buffered requests.
                 */
            rejectAll: function (reason) {
                if (reason) {
                    for (var i = 0; i < buffer.length; ++i) {
                        buffer[i].deferred.reject(reason);
                    }
                }
                buffer = [];
            },

            /**
                 * Retries all the buffered requests clears the buffer.
                 */
            retryAll: function () {
                for (var i = 0; i < buffer.length; ++i) {
                    retryHttpRequest(buffer[i].config, buffer[i].deferred);
                }
                buffer = [];
            }
        };
      }]);

and if you haven't enough you can also add an interceptor, that triggers an auth change event, if the server response is unauthorized:

如果你还不够,你还可以添加一个拦截器,如果服务器响应未经授权,它会触发一个auth change事件:

    angular.module('app')
      .factory('AuthInterceptor', ["$rootScope", "$q", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $q, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {

        return {
            request: function (config) {
                config.headers = config.headers || {};
                if (AuthSession.token) {
                    config.headers.Authorization = 'Bearer ' + AuthSession.token.TokenKey;
                }
                return config;
            },
            responseError: function (rejection) {
                if (rejection.status === 401) {
                    var deferred = $q.defer();

                    AuthHttpBuffer.append(rejection.config, deferred);

                    if (AuthSession.token) {
                        $rootScope.$broadcast('auth-change', AUTH_EVENTS.notAuthorized);
                    } else {
                        $rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired);
                    }
                    return deferred.promise;
                }
                return $q.reject(rejection);
            }
        }
      }]);

this interceptor also adds a session token to all requests if available.

此拦截器还会向所有请求添加会话令牌(如果可用)。

to use this interceptor, you have to add the following two lines to your app.config():

要使用此拦截器,您必须将以下两行添加到app.config():

    $httpProvider.defaults.withCredentials = true;
    $httpProvider.interceptors.push("AuthInterceptor");

#1


2  

If you use ui-router, you can do something similar to this:

如果你使用ui-router,你可以做类似的事情:

First introduce some kind of access-levels to your states

首先向您的州介绍某种访问级别

$stateProvider
        .state('admin', {
            url: "/admin",
            templateUrl: "/app/views/admin.html",
            controller: "AdminController",
            data: {
                accessLevel: 'admin'
            }
        })

then you have to check on state change, if your logged in user has the required access-level:

如果您的登录用户具有所需的访问级别,则必须检查状态更改:

You can create an auth service which implements your logic to log your user in, as example you can use this service

您可以创建一个auth服务,该服务实现您的逻辑以记录您的用户,例如您可以使用此服务

angular.module('app')
   .factory("AuthService", ["$rootScope", "$http", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $http, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {

        function loginFailed() {
            $rootScope.$broadcast("auth-change", AUTH_EVENTS.loginFailed);
        };

        AuthSession.load();

        $rootScope.$on('$stateChangeStart', function (event, nextState) {
            if (nextState.data && nextState.data.accessLevel && !service.isAuthorized(nextState.data.accessLevel)) {
                event.preventDefault();
                $rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired, nextState.name);
            }
        });

        var service = {
            login: function (credentials) {
                return $http
                            .post('/api/account/login', credentials)
                            .success(function (data, status) {
                                if ((status < 200 || status >= 300) && data.length >= 1) {
                                    loginFailed();
                                    return;
                                }

                                AuthSession.create(data.AccessToken, data.User);
                                $rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
                                AuthHttpBuffer.retryAll();
                            }).error(function (data, status) {
                                loginFailed();
                            });
            },
            cancel: function () {
                AuthHttpBuffer.rejectAll();
            },
            logout: function () {
                AuthSession.destroy();
                $rootScope.$broadcast("auth-change", AUTH_EVENTS.logoutSuccess);
            },
            isAuthenticated: function () {
                return (AuthSession.token !== null);
            },
            isAuthorized: function (accessLevel) {
                if (!accessLevel) return true;

                return (this.isAuthenticated() && AuthSession.user.UserRoles.indexOf(accessLevel) !== -1);
            }

        }
        return service;
    }]);

and your AuthSession service:

和您的AuthSession服务:

angular.module('app')
      .factory("AuthSession", ["$rootScope", "$window", "AUTH_EVENTS", function ($rootScope, $window, AUTH_EVENTS) {

        var sessionService = {
            user: null,
            token: null,

            //load the stored session data
            load: function () {
                var user = ...yourdata... //TODO implement load user data;
                var token = ...yourdata... //implement load user data;

                if (!user || !token) return;

                if (!this.checkTokenExpiration(token)) return;

                this.user = user;
                this.token = token;

                $rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
            },

            //save the current data to the session storage
            save: function () {
                //TODO save your userdata/token etc.
            },

            //create the current user with the assosiated token
            create: function (token, user) {
                this.token = token;
                this.user = user;

                if (!angular.isArray(this.user.UserRoles))
                    this.user.UserRoles = [this.user.UserRoles];

                this.save();
            },

            //destroy an user with all assosiated data
            destroy: function () {
                this.token = null;
                this.user = null;

                //TODO clear your saved data here
            },

            //check if the supplied access token data is expired
            checkTokenExpiration: function (token) {
                if (token === undefined || token === null) return false;

                var retval = (new Date(token.TokenExpires).getTime() > new Date().getTime());

                if (retval === false) {
                    sessionService.destroy();
                    $rootScope.$broadcast("auth-change", AUTH_EVENTS.sessionTimeout);
                }

                return retval;
            }
        }

        return sessionService;

      }]);

and the constants:

和常数:

angular.module('app')
      .constant('AUTH_EVENTS', {
        loginSuccess: 'auth-login-success',
        loginFailed: 'auth-login-failed',
        logoutSuccess: 'auth-logout-success',
        loginRequired: 'auth-login-required',
        sessionTimeout: 'auth-session-timeout',
        notAuthorized: 'auth-not-authorized'
      });

If you want be able to catch urls, where you haven't the right accesrights, you can send the request to a http buffer:

如果您希望能够捕获您没有正确访问权限的URL,则可以将请求发送到http缓冲区:

angular.module('app')
      .factory('AuthHttpBuffer', ["$injector", function ($injector) {
        /** Holds all the requests, so they can be re-requested in future. */
        var buffer = [];

        /** Service initialized later because of circular dependency problem. */
        var $http;

        function retryHttpRequest(config, deferred) {
            function successCallback(response) {
                deferred.resolve(response);
            }
            function errorCallback(response) {
                deferred.reject(response);
            }
            $http = $http || $injector.get('$http');
            $http(config).then(successCallback, errorCallback);
        }

        return {
            /**
                 * Appends HTTP request configuration object with deferred response attached to buffer.
                 */
            append: function (config, deferred) {
                buffer.push({
                    config: config,
                    deferred: deferred
                });
            },

            /**
                 * Abandon or reject (if reason provided) all the buffered requests.
                 */
            rejectAll: function (reason) {
                if (reason) {
                    for (var i = 0; i < buffer.length; ++i) {
                        buffer[i].deferred.reject(reason);
                    }
                }
                buffer = [];
            },

            /**
                 * Retries all the buffered requests clears the buffer.
                 */
            retryAll: function () {
                for (var i = 0; i < buffer.length; ++i) {
                    retryHttpRequest(buffer[i].config, buffer[i].deferred);
                }
                buffer = [];
            }
        };
      }]);

and if you haven't enough you can also add an interceptor, that triggers an auth change event, if the server response is unauthorized:

如果你还不够,你还可以添加一个拦截器,如果服务器响应未经授权,它会触发一个auth change事件:

    angular.module('app')
      .factory('AuthInterceptor', ["$rootScope", "$q", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $q, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {

        return {
            request: function (config) {
                config.headers = config.headers || {};
                if (AuthSession.token) {
                    config.headers.Authorization = 'Bearer ' + AuthSession.token.TokenKey;
                }
                return config;
            },
            responseError: function (rejection) {
                if (rejection.status === 401) {
                    var deferred = $q.defer();

                    AuthHttpBuffer.append(rejection.config, deferred);

                    if (AuthSession.token) {
                        $rootScope.$broadcast('auth-change', AUTH_EVENTS.notAuthorized);
                    } else {
                        $rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired);
                    }
                    return deferred.promise;
                }
                return $q.reject(rejection);
            }
        }
      }]);

this interceptor also adds a session token to all requests if available.

此拦截器还会向所有请求添加会话令牌(如果可用)。

to use this interceptor, you have to add the following two lines to your app.config():

要使用此拦截器,您必须将以下两行添加到app.config():

    $httpProvider.defaults.withCredentials = true;
    $httpProvider.interceptors.push("AuthInterceptor");