Angular/Breeze登录实现



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

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

我面临着以下挑战:

  1. 我无法显示默认登录页面,我总是得到仪表板作为默认页面。我正在寻找在哪里我可以路由到登录页面。2.breezcontroller——这是内部控制器,我需要在这里写我的登录方法吗?

总而言之,我正在寻找一个完整的登录功能实现遵循我的架构/代码结构。

下面是一个可以在基于angular的SPA中使用的方法的描述。这个特定的示例使用基于令牌的OAuth身份验证,但可以适应其他身份验证方案。它松散地基于基于AngularJS(或类似)的应用程序

中描述的认证方法。

一些亮点是:

  • 通过auth服务管理认证

  • HTTP请求被拦截,并且:

    • 当检测到401(拒绝访问)错误且没有用户登录时,在$rootScope

    • 上发出auth:login事件(注意-不广播)
    • 如果在用户登录时检测到401错误,并且OAuth刷新令牌可用,则尝试基于刷新令牌获取新的访问令牌。auth:login事件仅在令牌无法刷新时触发。

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

  • 应用程序应该监视auth:login事件并提示用户输入凭据。(我使用Angular-UI Bootstrap模态对话框来做这件事。)一旦提供了凭据,就必须调用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);
            }
        };
    }]);
}]);