我在angularJS SPA中实现登录系统存在严重问题。这是整个应用程序开发中最难的部分,因为我已经处理过其他方面。我无法提出正确的服务设计(我认为我已经理解了承诺,但似乎还没那么好)。
app的唯一授权方法是FB登录与本机服务器端逻辑相结合。在FB JS SDK登录成功应用程序将使http发布到服务器,我使用FB PHP SDK从fb cookie获取访问令牌,获取fb id并检查我们是否有用户在数据库中具有该fb id。如果有 - 将所有用户凭据返回给应用程序,如果没有 - 返回注册标志。
对于注册我只需要一件FB没有提供的东西 - 用户将在地图上放置一个标记,坐标将被发送到服务器并保存在数据库中。
mysql> DESCRIBE users;
+---------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fbid | ... | NO | | NULL | |
| lat | ... | NO | | NULL | |
| lng | ... | NO | | NULL | |
| name | ... | NO | | NULL | |
| ... | ... | NO | | NULL | |
+---------+------------+------+-----+---------+----------------+
我将使用fbid授权(识别)用户,我自己的id在app(客户端和服务器端)内工作,坐标来自寄存器以及FB中的所有其他内容。
我可以通过4种方式从app登录用户:
status === connected
然后我自动登录用户。我如何看待它,每个案例都依赖于一种方法,让我们说AuthService.login
:
AuthService.login
app.run()
方法调用connected
AuthService.login
时称为transitionHookFn
转换生命周期添加onStart
并运行AuthService.login如果需要登录且用户未登录。成功登录后我继续,否则 - 取消转换(我使用ui-路由器)$httpProvider.interceptor
通过AuthService.login
拨打statuss 401
$ http响应。在成功登录时,我再次请求相同的请求,否则 - 让请求失败(并处理路由逻辑,以便失败的请求将取消转换)我的核心问题在于AuthService.login
。应该如何构建该方法,以便我可以在所有四种情况下重用它?
到目前为止我有什么:
我使用ui-router和this来集成FB JS SDK。 为了处理应用程序中的用户状态我有服务:
(function(){
angular.module('userService')
.factory('UserService', userService);
userService.$inject = [];
function userService(){
var user = {
isLogged: false,
info: ''
};
return {
setUser: setUser,
getUser: getUser,
clearUser: clearUser
};
function setUser(info){
user.isLogged = true;
user.info = info;
}
function getUser(){
return user;
}
function clearUser(){
user.isLogged = false;
user.info = {};
}
}
})();
我将其注入我需要了解用户身份的位置并使用userService.getUser
。
授权我有服务:
(function(){
angular.module('authService')
.factory('AuthService', AuthService);
AuthService.$inject = ['UserService', 'Facebook', '$http', 'baseUrl', '$q'];
function AuthService(UserService, Facebook, $http, baseUrl, $q){
return {
autoLogin: autoLogin,
login: login
};
function autoLogin(){
Facebook.getLoginStatus(function(response) {
statusChangeCallback(response);
});
}
function login(){
Facebook.login(function(response) {
statusChangeCallback(response);
});
}
function statusChangeCallback(response) {
console.log('statusChangeCallback');
if (response.status === 'connected') {
$http({
method: 'POST',
url: /*login endpoint*/
}).then(/*Dont know what should do here*/)
}
}
}
})();
此时登录端点返回JSON {user: null, register: true}
,如果它是第一次登录并{user: {/*data*/}, register: false}
但我在那里打开更改。我已经尝试了几种处理响应的方法,但在前面描述的四种情况之一中我总是遇到一些麻烦。基本上如果register: false
,那么我应该使用userService.setUser(response.data.user)
,如果register:true
- $state.target('register')
对于案例3我有:
$transition.onStart({ to: function(state) {
return state.data != null && state.data.authRequired === true;
}},function(trans){
var AuthService = trans.injector().get('AuthService');
/*Should run AuthService.login and:
1.continue if completely logged in
2.$state.target('register') if register needed
3.cancel otherwise*/
});
对于案例4我有:
var sessionRecoverer = {
responseError: function(response) {
if (response.status == 401){
var AuthService = $injector.get('AuthService');
var $http = $injector.get('$http');
var deferred = $q.defer();
/*again call login and redirect if register or do http request again if login success*/
}
return $q.reject(response); //Think that this would be correct to reject any other error
}
};
总结一下 - 我理解$ http拦截器和ui-router转换是如何工作的。我不知道如何处理来自AuthService的承诺,因为服务器响应有两种不同的结果,我很难理解如何在AuthService登录流程中保留UserService.setUser方法。前两种情况(自动登录和用户需求登录)很简单 - AuthService需要设置用户信息或重定向到注册。最后两个选项出现问题,因为在这两个选项中,其他一些服务需要确定是否继续。
更新:在我的脑海中只是一个想法 - 如果需要注册,我会返回自定义statuss代码或标头,使用http拦截器和重定向+失败请求捕获它。然后我会有简单的机制,如果AuthService返回承诺考虑登录成功否则失败。 ?
答案 0 :(得分:3)
我正在处理一个类似的AngularJS项目的身份验证/授权工作流程。我没有使用PHP或Facebook IDP,而是使用ASP.NET Web API和Microsoft Azure B2C作为IDP。但是,让我们从AngularJS的角度来看这个问题,并对IDP和服务器端技术不可知。
最简单的流程涉及使应用程序的根(主页)成为未经过身份验证的页面。用户将单击“登录”按钮,该按钮将启动您的身份验证过程。这会调用您的AuthService.login()
函数。此调用应该来自控制器,并且应该包含在承诺中以避免使用回调。 (注意:您可以在.run()
块中完全拥有自动登录逻辑,而不是按下或这两个按钮,因为它似乎是您想做的 - 逻辑将在.run()
区块中保持不变。
function login(){
var deferred = this.$q.defer();
Facebook.login(function(response) {
deferred.resolve(response);
});
return deferred.promise;
}
然后,从调用AuthService.login()
的控制器,您需要处理对API的POST请求以更新/获取用户信息。这看起来像这样(注意我将你的HTTP调用重构为一个新的initUserData()
函数):
AuthService.login().then(function(response) {
if (response.status === 'connected') {
AuthService.initUserData().then(function(userObject) {
if(userObject.data.register === true) {
$state.go('register');
} else {
userService.setUser(response.data.user);
$state.go('whereverUserShouldGoInThisCase');
}
});
}
});
很好,到目前为止我们已经涵盖了#1和#2案例。我们来看看#3 。
如果我们不希望用户点击需要身份验证的路由,我们可以使用应用程序的.run()
块中的UI-Router设置挂钩。
首先,我们需要声明哪些路由需要进行身份验证。
.state('home', {
url: 'home',
restricted: true
})
我们可以通过在您的应用程序中为您想要限制(通过身份验证)的所有路由添加任何名称的新属性(在本例中我们使用restricted
)来实现此目的。
然后在.run()
块中,我们通过$stateChangeStart
挂钩到UI-Router。无论何时转换到状态(在转换的开始),包括抽象状态,都会调用此方法。
$rootScope.$on('$stateChangeStart', (event, next, nextParams, current, currentParams), function() {
// Any time the user tries to hit a restricted route, we need to check if they're logged in
if (next.restricted) {
AuthService.login().then(function(response) {
if(response.status !== 'connected') {
event.preventDefault(); // Prevent going to the transitioning state
$state.go('login'); // Go to the state we want to send the user to
}
});
}
});
这应该涵盖#3,让我们看看#4 。 你肯定有使用HTTP拦截器的正确想法。在这里,我们可以复制与以前相同的逻辑来处理用户在401上重定向的位置。
responseError: function(response) {
if (response.status == 401){
var AuthService = $injector.get('AuthService');
AuthService.login().then(function(response) {
if(response.status === 'connected') {
AuthService.initUserData().then(function(userObject) {
if(userObject.data.register === true) {
$injector.get('$state').go('register');
} else {
userService.setUser(response.data.user);
$injector.get('$state').go('whereverUserShouldGoInThisCase');
}
});
}
});
}
return $q.reject(response); // Yes, this will properly reject your response
}
最后的一些说明:
$stateChangeStart
。这个钩子非常强大,当你有不同的“角色”(即注册/非注册用户)时,你可以使用上一个/下一个路径,这对你的应用程序中的路由逻辑有帮助。deferred.reject()
。