我有一个使用Angular和Breeze的SPA应用程序,我需要实现登录功能,我是Angular/Breeze的新手。我的架构/代码结构如下所述:
login.html -> login.js ->datacontext/Service.js->entityManagerFactory-> breezcontroller .cs ->repository->dbcontext->database.
我面临着以下挑战:
- 我无法显示默认登录页面,我总是得到仪表板作为默认页面。我正在寻找在哪里我可以路由到登录页面。2.breezcontroller——这是内部控制器,我需要在这里写我的登录方法吗?
总而言之,我正在寻找一个完整的登录功能实现遵循我的架构/代码结构。
下面是一个可以在基于angular的SPA中使用的方法的描述。这个特定的示例使用基于令牌的OAuth身份验证,但可以适应其他身份验证方案。它松散地基于基于AngularJS(或类似)的应用程序
中描述的认证方法。一些亮点是:
通过
auth
服务管理认证HTTP请求被拦截,并且:
当检测到401(拒绝访问)错误且没有用户登录时,在
$rootScope
上发出如果在用户登录时检测到401错误,并且OAuth刷新令牌可用,则尝试基于刷新令牌获取新的访问令牌。
auth:login
事件仅在令牌无法刷新时触发。一旦用户登录,包含用户访问令牌的
Authorization
头被插入到每个HTTP请求中,以便服务器可以对用户进行身份验证。
auth:login
事件(注意-不广播)应用程序应该监视
例如: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.sessionStorage
或window.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);
}
};
}]);
}]);