Angular / Breeze Login实现

时间:2014-07-15 18:54:05

标签: angularjs asp.net-web-api breeze asp.net-web-api-routing

我有一个使用Angular和Breeze的SPA应用程序,我需要实现登录功能,我是Angular / Breeze的新手。我的架构/代码结构如下所述:

  

login.html - > login.js - > datacontext / Service.js ---> entityManagerFactory - > breezecontroller.cs - > repository-> dbcontext - > database。

我正面临以下挑战:

  1. 我无法将登录页面显示为默认页面,我总是将Dashboard作为默认页面。我正在寻找可以路由到登录页面的地方。 2.breezecontroller - 这是内部控制器,我需要在这里编写我的登录方法吗?
  2. 总而言之,我正在寻找一个完整的登录功能实现,它遵循我的架构/代码结构。

1 个答案:

答案 0 :(得分:2)

以下是可在基于角度的SPA中使用的方法的说明。此特定示例使用基于令牌的OAuth身份验证,但可以适用于其他身份验证方案。它松散地基于Authentication in AngularJS (or similar) based application

中描述的方法

一些亮点是:

  • 通过auth服务管理身份验证。

  • 拦截HTTP请求,并且:

    • 当检测到401(访问被拒绝)错误并且没有用户登录时,auth:login

      上会发出$rootScope事件(注意 - 未广播)
    • 如果在用户登录并且OAuth刷新令牌可用时检测到401错误,则会尝试根据刷新令牌获取新的访问令牌。只有在无法刷新令牌时才会发出auth:login事件。

    • 用户登录后,会在每个HTTP请求中插入包含用户访问令牌的Authorization标头,以便服务器可以对用户进行身份验证。

  • 应用程序应该监视auth:login个事件并提示用户输入凭据。 (我使用Angular-UI Bootstrap modal dialog来执行此操作。)提供凭据后,必须调用auth服务的login函数才能完成登录。调用login后,将重试所有最初因401错误而失败的待处理HTTP请求。或者,可以调用auth服务的loginCancelled函数来取消登录,这将拒绝所有挂起的HTTP请求。

    例如:

angular.module('app', ['auth'])
.run(['$rootScope', 'auth', function ($rootScope,  auth) {
    $rootScope.$on(auth.options.loginRequiredEvent, function (event, details) {
        // Display login dialog here, which will ultimately
        // call `auth.login` or `auth.loginCancelled`
    });

    auth.restoreAuthDataFromStorage();
}]);

以下是用户提供凭据后调用auth.login的示例:

auth.login(userName, password, isPersistent)
    .success(function () {
        // Dismiss login dialog here
    })
    .error(function (data, status) {
        if (status === 401 || (data && data.error === 'invalid_grant')) {
            failureMessage = 'Log in failed: Bad username or password';
        } else {
            failureMessage = 'Log in failed: Unexpected error';
        }
    });
  • 登录用户的详细信息存储在window.sessionStoragewindow.localStorage中(基于是否已请求持久登录),以便能够跨页面加载访问。

最后,这是auth服务本身。

var module = angular.module('auth');

module.provider('auth', function () {
    var authOptions = {
        tokenUrl: '/OAuthToken',
        loginRequiredEvent: 'auth:loginRequired',
        logoffEvent: 'auth:logoff',
        loginEvent: 'auth:login',
        authTokenKey: 'auth:accessToken'
    };

    this.config = function (options) {
        angular.extend(authOptions, options);
    };

    // Get the auth service
    this.$get = ['$rootScope', '$http', '$q', function ($rootScope, $http, $q) {
        var authData = {
            // Filled as follows when authenticated:
            // currentUserName: '...',
            // accessToken: '...',
            // refreshToken: '...',
        };

        var httpRequestsPendingAuth = new HttpRequestsPendingAuthQueue();

        // Public service API
        return {
            login: login,
            refreshAccessToken: refreshAccessToken,
            loginCancelled: loginCancelled,
            logoff: logoff,
            currentUserName: function () { return authData.currentUserName; },
            isAuthenticated: function () { return !!authData.accessToken; },
            getAccessToken: function () { return authData.accessToken; },
            restoreAuthDataFromStorage: restoreAuthDataFromStorage,
            _httpRequestsPendingAuth: httpRequestsPendingAuth,
            options: authOptions,
        };

        function isAuthenticated() {
            return !!authData.accessToken;
        };

        function restoreAuthDataFromStorage() {
            // Would be better to use an Angular service to access local storage
            var dataJson = window.sessionStorage.getItem(authOptions.authTokenKey) || window.localStorage.getItem(authOptions.authTokenKey);

            authData = (dataJson ? JSON.parse(dataJson) : {});
        }

        function accessTokenObtained(data) {
            if (!data || !data.access_token) {
                throw new Error('No token data returned');
            }

            angular.extend(authData, {
                accessToken: data.access_token,
                refreshToken: data.refresh_token
            });

            // Would be better to use an Angular service to access local storage
            var storage = (authData.isPersistent ? window.localStorage : window.sessionStorage);
            storage.setItem(authOptions.authTokenKey, JSON.stringify(authData));

            httpRequestsPendingAuth.retryAll($http);
        }

        function login(userName, password, isPersistent) {
            // Data for obtaining token must be provided in a content type of application/x-www-form-urlencoded
            var data = 'grant_type=password&username=' + encodeURIComponent(userName) + '&password=' + encodeURIComponent(password);

            return $http
                .post(authOptions.tokenUrl, data, { ignoreAuthFailure: true })
                .success(function (data) {
                    authData = {
                        currentUserName: userName,
                        isPersistent: isPersistent
                    };

                    accessTokenObtained(data);

                    $rootScope.$emit(authOptions.loginEvent);
                })
                .error(function () {
                    logoff();
                });
        }

        function refreshAccessToken() {
            if (!authData.refreshToken) {
                logoff();
                return $q.reject('No refresh token available');
            }

            // Data for obtaining token must be provided in a content type of application/x-www-form-urlencoded
            var data = 'grant_type=refresh_token&refresh_token=' + encodeURIComponent(authData.refreshToken);

            return $http
                .post(authOptions.tokenUrl, data, { ignoreAuthFailure: true })
                .success(function (data) { accessTokenObtained(data); })
                .error(function () { logoff(); });
        }

        function loginCancelled() {
            httpRequestsPendingAuth.rejectAll();
        }

        function logoff() {
            // Would be better to use an Angular service to access local storage
            window.sessionStorage.removeItem(authOptions.authTokenKey);
            window.localStorage.removeItem(authOptions.authTokenKey);

            if (isAuthenticated()) {
                authData = {};

                $rootScope.$emit(authOptions.logoffEvent);
            }
        }

        // Class implementing a queue of HTTP requests pending authorization
        function HttpRequestsPendingAuthQueue() {
            var q = [];

            this.append = function (rejection, deferred) {
                q.push({ rejection: rejection, deferred: deferred });
            };

            this.rejectAll = function () {
                while (q.length > 0) {
                    var r = q.shift();
                    r.deferred.reject(r.rejection);
                }
            };

            this.retryAll = function ($http) {
                while (q.length > 0) {
                    var r = q.shift();
                    retryRequest($http, r.rejection.config, r.deferred);
                }
            };

            function retryRequest($http, config, deferred) {
                var configToUse = angular.extend(config, { ignoreAuthFailure: true });

                $http(configToUse)
                    .then(function (response) {
                        deferred.resolve(response);
                    }, function (response) {
                        deferred.reject(response);
                    });
            }
        }
    }];
});

module.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(['$injector', '$rootScope', '$q', function ($injector, $rootScope, $q) {
        var auth;

        return {
            // Insert an "Authorization: Bearer <token>" header on each HTTP request
            request: function (config) {
                auth = auth || $injector.get('auth');

                var token = auth.getAccessToken();
                if (token) {
                    config.headers = config.headers || {};
                    config.headers.Authorization = 'Bearer ' + token;
                }

                return config;
            },

            // Raise a "login required" event upon "401 access denied" responses on HTTP requests
            responseError: function(rejection) {
                if (rejection.status === 401 && !rejection.config.ignoreAuthFailure) {
                    var deferred = $q.defer();

                    auth = auth || $injector.get('auth');

                    auth._httpRequestsPendingAuth.append(rejection, deferred);

                    if (auth.isAuthenticated()) {
                        auth.refreshAccessToken().then(null, function () {
                            $rootScope.$emit(auth.options.loginRequiredEvent, { message: 'Login session has timed out. Please log in again.' });
                        });
                    } else {
                        // Not currently logged in and a request for a protected resource has been made: ask for a login
                        $rootScope.$emit(auth.options.loginRequiredEvent, { rejection: rejection });
                    }

                    return deferred.promise;
                }

                // otherwise, default behaviour
                return $q.reject(rejection);
            }
        };
    }]);
}]);