角度ui路由器登录认证

我是AngularJS的新手,我有点困惑于如何在以下场景中使用angular-“ui-router”:

我正在构建一个由两部分组成的Web应用程序。 第一部分是具有登录和注册视图的主页,第二部分是仪表板(成功登录后)。

我已经创建了一个index.html的角度的应用程序和ui-router配置来处理/login/signup视图的主页部分,还有另一个文件dashboard.html仪表板部分与其应用程序和ui-router配置处理很多子视图。

现在我完成了仪表板部分,不知道如何将两个部分与不同的角度应用程序结合起来。 我怎么能告诉家里的应用程序重定向到仪表板应用程序?

    我正在做一个更好的演示,以及清理一些这些服务到一个可用的模块,但这是我想出了。 这是一个复杂的过程来解决一些警告,所以挂在那里。 你需要把它分成几块。

    看看这个庞然大物 。

    首先,你需要一个服务来存储用户的身份。 我叫这个principal 。 可以检查用户是否已登录,并根据请求可以解析代表用户身份基本信息的对象。 这可以是你需要的任何东西,但是要点是显示名称,用户名,可能是电子邮件,以及用户所属的角色(如果这适用于你的应用程序)。 校长也有方法来做角色检查。

     .factory('principal', ['$q', '$http', '$timeout', function($q, $http, $timeout) { var _identity = undefined, _authenticated = false; return { isIdentityResolved: function() { return angular.isDefined(_identity); }, isAuthenticated: function() { return _authenticated; }, isInRole: function(role) { if (!_authenticated || !_identity.roles) return false; return _identity.roles.indexOf(role) != -1; }, isInAnyRole: function(roles) { if (!_authenticated || !_identity.roles) return false; for (var i = 0; i < roles.length; i++) { if (this.isInRole(roles[i])) return true; } return false; }, authenticate: function(identity) { _identity = identity; _authenticated = identity != null; }, identity: function(force) { var deferred = $q.defer(); if (force === true) _identity = undefined; // check and see if we have retrieved the // identity data from the server. if we have, // reuse it by immediately resolving if (angular.isDefined(_identity)) { deferred.resolve(_identity); return deferred.promise; } // otherwise, retrieve the identity data from the // server, update the identity object, and then // resolve. // $http.get('/svc/account/identity', // { ignoreErrors: true }) // .success(function(data) { // _identity = data; // _authenticated = true; // deferred.resolve(_identity); // }) // .error(function () { // _identity = null; // _authenticated = false; // deferred.resolve(_identity); // }); // for the sake of the demo, fake the lookup // by using a timeout to create a valid // fake identity. in reality, you'll want // something more like the $http request // commented out above. in this example, we fake // looking up to find the user is // not logged in var self = this; $timeout(function() { self.authenticate(null); deferred.resolve(_identity); }, 1000); return deferred.promise; } }; } ]) 

    其次,您需要一个服务来检查用户想要访问的状态,确保他们已经登录(如有必要,不需要登录,重置密码等),然后进行角色检查(如果您的应用程序需要这个)。 如果未通过身份验证,请将其发送到登录页面。 如果它们通过身份验证,但未通过角色检查,请将其发送到拒绝访问页面。 我打电话给这个服务authorization

     .factory('authorization', ['$rootScope', '$state', 'principal', function($rootScope, $state, principal) { return { authorize: function() { return principal.identity() .then(function() { var isAuthenticated = principal.isAuthenticated(); if ($rootScope.toState.data.roles && $rootScope.toState .data.roles.length > 0 && !principal.isInAnyRole( $rootScope.toState.data.roles)) { if (isAuthenticated) { // user is signed in but not // authorized for desired state $state.go('accessdenied'); } else { // user is not authenticated. Stow // the state they wanted before you // send them to the sign-in state, so // you can return them when you're done $rootScope.returnToState = $rootScope.toState; $rootScope.returnToStateParams = $rootScope.toStateParams; // now, send them to the signin state // so they can log in $state.go('signin'); } } }); } }; } ]) 

    现在你只需要在ui-router$stateChangeStart中听一下。 这给你一个机会来检查当前的状态,他们想要去的状态,并插入你的授权检查。 如果失败,您可以取消路由转换,或更改为其他路由。

     .run(['$rootScope', '$state', '$stateParams', 'authorization', 'principal', function($rootScope, $state, $stateParams, authorization, principal) { $rootScope.$on('$stateChangeStart', function(event, toState, toStateParams) { // track the state the user wants to go to; // authorization service needs this $rootScope.toState = toState; $rootScope.toStateParams = toStateParams; // if the principal is resolved, do an // authorization check immediately. otherwise, // it'll be done when the state it resolved. if (principal.isIdentityResolved()) authorization.authorize(); }); } ]); 

    关于跟踪用户身份的棘手部分是,如果您已经通过身份验证(例如,您在之前的会话之后访问该页面,并在Cookie中保存了身份验证令牌,或者您已经刷新页面,从一个链接放到一个URL上)。 由于ui-router工作方式,你需要在认证检查之前先做一次身份认证。 您可以使用状态配置中的resolve选项来执行此操作。 我有一个父状态的网站,所有国家继承,这迫使在任何事情发生之前解决委托人。

     $stateProvider.state('site', { 'abstract': true, resolve: { authorize: ['authorization', function(authorization) { return authorization.authorize(); } ] }, template: '<div ui-view />' }) 

    这里还有另外一个问题,只resolve一次。 一旦您对身份查询的承诺完成,它就不会再运行解析委托。 因此,我们必须在两个地方进行身份验证检查:一次根据您的身份承诺resolve ,其中涵盖您的应用程序第一次加载,一旦在$stateChangeStart如果解决方案已经完成,这涵盖了任何时候你浏览状态。

    好的,到目前为止我们做了什么?

    1. 如果用户登录,我们会检查该应用何时加载。
    2. 我们跟踪有关登录用户的信息。
    3. 我们将它们重定向到登录状态,以便要求用户登录的状态。
    4. 如果他们没有访问权限,我们将他们重定向到拒绝访问状态。
    5. 如果我们需要他们登录,我们有一个机制可以将用户重定向到他们请求的原始状态。
    6. 我们可以签署一个用户(需要与管理您的身份验证票证的任何客户端或服务器代码协调一致)。
    7. 我们无需每次重新加载浏览器或链接时都将用户发送回登录页面。

    我们从哪里出发? 那么,您可以将您的状态组织到需要登录的区域。您可以通过向这些状态添加具有roles data (如果要使用继承,则可以要求授权用户)。 在这里,我们将资源限制到管理员:

     .state('restricted', { parent: 'site', url: '/restricted', data: { roles: ['Admin'] }, views: { 'content@': { templateUrl: 'restricted.html' } } }) 

    现在,您可以控制用户可以访问路线的状态。 还有其他问题吗? 根据是否登录,可能只是改变视图的一部分? 没问题。 使用principal.isAuthenticated()或甚至是principal.isInRole() ,可以使用许多方法来有条件地显示模板或元素。

    首先,将principal注入控制器或其他任何东西,并将其粘贴到范围内,以便在视图中轻松使用它:

     .scope('HomeCtrl', ['$scope', 'principal', function($scope, principal) { $scope.principal = principal; }); 

    显示或隐藏一个元素:

     <div ng-show="principal.isAuthenticated()"> I'm logged in </div> <div ng-hide="principal.isAuthenticated()"> I'm not logged in </div> 

    等等,等等。 无论如何,在你的示例应用程序,你会有一个主页的状态,让未经验证的用户下降。 他们可以链接到登录或注册状态,或者将这些表单内置到该页面中。 什么都适合你。

    仪表板页面可以全部从需要用户登录的状态继承,并且也可以是User角色成员。 我们讨论的所有授权内容都将从那里流出。

    到目前为止发布的解决方案在我看来是不必要的复杂。 有一个更简单的方法。 ui-router的文档说听$locationChangeSuccess并使用$urlRouter.sync()检查状态转换,暂停或恢复。 但即便如此,也不行。

    但是,这里有两个简单的选择。 选一个:

    解决方案1:侦听$locationChangeSuccess

    你可以听取$locationChangeSuccess ,你可以执行一些逻辑,甚至是异步逻辑。 基于这个逻辑,你可以让函数返回undefined,这将导致状态转换继续正常,或者你可以做$state.go('logInPage') ,如果用户需要认证的话。 这是一个例子:

     angular.module('App', ['ui.router']) // In the run phase of your Angular application .run(function($rootScope, user, $state) { // Listen to '$locationChangeSuccess', not '$stateChangeStart' $rootScope.$on('$locationChangeSuccess', function() { user .logIn() .catch(function() { // log-in promise failed. Redirect to log-in page. $state.go('logInPage') }) }) }) 

    请记住,这实际上并不阻止目标状态加载,但如果用户未经授权,它会重定向到登录页面。 没关系,因为真正的保护在服务器上,无论如何。

    解决方案2:使用状态resolve

    在此解决方案中,您使用ui-router解析功能 。

    如果用户未通过身份验证,则基本上拒绝resolve承诺,然后将其重定向到登录页面。

    这是怎么回事:

     angular.module('App', ['ui.router']) .config( function($stateProvider) { $stateProvider .state('logInPage', { url: '/logInPage', templateUrl: 'sections/logInPage.html', controller: 'logInPageCtrl', }) .state('myProtectedContent', { url: '/myProtectedContent', templateUrl: 'sections/myProtectedContent.html', controller: 'myProtectedContentCtrl', resolve: { authenticate: authenticate } }) .state('alsoProtectedContent', { url: '/alsoProtectedContent', templateUrl: 'sections/alsoProtectedContent.html', controller: 'alsoProtectedContentCtrl', resolve: { authenticate: authenticate } }) function authenticate($q, user, $state, $timeout) { if (user.isAuthenticated()) { // Resolve the promise successfully return $q.when() } else { // The next bit of code is asynchronously tricky. $timeout(function() { // This code runs after the authentication promise has been rejected. // Go to the log-in page $state.go('logInPage') }) // Reject the authentication promise to prevent the state from loading return $q.reject() } } } ) 

    与第一种解决方案不同,该解决方案实际上阻止了目标状态的加载。

    最简单的解决方案是使用$stateChangeStartevent.preventDefault()取消用户未通过身份验证时的状态更改,并将其重定向到作为登录页面的身份验证状态。

     angular .module('myApp', [ 'ui.router', ]) .run(['$rootScope', 'User', '$state', function ($rootScope, User, $state) { $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { if (toState.name !== 'auth' && !User.authenticaded()) { event.preventDefault(); $state.go('auth'); } }); }] ); 

    我认为你需要一个处理认证过程(及其存储)的服务。

    在这个服务中你需要一些基本的方法:

    • isAuthenticated()
    • login()
    • logout()
    • 等等

    这个服务应该注入你的每个模块的控制器中:

    • 在仪表板部分中,使用此服务检查用户是否已通过身份验证( service.isAuthenticated()方法)。 如果没有,重定向到/登录
    • 在您的登录部分,只需使用表单数据通过service.login()方法对用户进行身份验证即可

    这个行为的一个好的和健壮的例子是项目angular-app ,特别是基于真棒HTTP Auth拦截器模块的安全模块

    希望这可以帮助

    我创建了这个模块来帮助这个过程小菜一碟

    你可以做这样的事情:

     $routeProvider .state('secret', { ... permissions: { only: ['admin', 'god'] } }); 

    或者也

     $routeProvider .state('userpanel', { ... permissions: { except: ['not-logged-in'] } }); 

    这是全新的,但值得一试!

    https://github.com/Narzerus/angular-permission

    我想与ui路由器1.0.0.X共享另一个解决方案

    您可能知道,stateChangeStart和stateChangeSuccess现在已被弃用。 https://github.com/angular-ui/ui-router/issues/2655

    相反,你应该使用$ transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html

    我是这样实现的:

    首先,我有和AuthService一些有用的功能

     angular.module('myApp') .factory('AuthService', ['$http', '$cookies', '$rootScope', function ($http, $cookies, $rootScope) { var service = {}; // Authenticates throug a rest service service.authenticate = function (username, password, callback) { $http.post('api/login', {username: username, password: password}) .success(function (response) { callback(response); }); }; // Creates a cookie and set the Authorization header service.setCredentials = function (response) { $rootScope.globals = response.token; $http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token; $cookies.put('globals', $rootScope.globals); }; // Checks if it's authenticated service.isAuthenticated = function() { return !($cookies.get('globals') === undefined); }; // Clear credentials when logout service.clearCredentials = function () { $rootScope.globals = undefined; $cookies.remove('globals'); $http.defaults.headers.common.Authorization = 'Bearer '; }; return service; }]); 

    然后我有这样的配置:

     angular.module('myApp', [ 'ui.router', 'ngCookies' ]) .config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/resumen'); $stateProvider .state("dashboard", { url: "/dashboard", templateUrl: "partials/dashboard.html", controller: "dashCtrl", data: { authRequired: true } }) .state("login", { url: "/login", templateUrl: "partials/login.html", controller: "loginController" }) }]) .run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService', function ($rootScope, $transitions, $state, $cookies, $http, AuthService) { // keep user logged in after page refresh $rootScope.globals = $cookies.get('globals') || {}; $http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals; $transitions.onStart({ to: function (state) { return state.data != null && state.data.authRequired === true; } }, function () { if (!AuthService.isAuthenticated()) { return $state.target("login"); } }); }]); 

    你可以看到我使用

     data: { authRequired: true } 

    标记只有通过身份验证才能访问的状态。

    然后,在.run上,我使用转换来检查已认证的状态

     $transitions.onStart({ to: function (state) { return state.data != null && state.data.authRequired === true; } }, function () { if (!AuthService.isAuthenticated()) { return $state.target("login"); } }); 

    我使用$ transitions文档中的一些代码来构建这个示例。 我很新的UI路由器,但它的工作原理。

    希望它可以帮助任何人。

    下面是我们如何摆脱无限循环,仍然使用$state.go而不是$location.path

     if('401' !== toState.name) { if (principal.isIdentityResolved()) authorization.authorize(); } 

    我有另一个解决方案:当你只有你想登录时显示的内容的时候,这个解决方案是完美的。定义一个规则,你检查你是否登录,而不是白名单路径的路径。

     $urlRouterProvider.rule(function ($injector, $location) { var UserService = $injector.get('UserService'); var path = $location.path(), normalized = path.toLowerCase(); if (!UserService.isLoggedIn() && path.indexOf('login') === -1) { $location.path('/login/signin'); } }); 

    在我的例子中,我问如果我没有登录,我想要路由的当前路由不是`/ login'的一部分,因为我的白名单路由如下

     /login/signup // registering new user /login/signin // login to app 

    所以我可以即时访问这两条路线,如果您在线,将检查其他所有路线。

    这是我登录模块的整个路由文件

     export default ( $stateProvider, $locationProvider, $urlRouterProvider ) => { $stateProvider.state('login', { parent: 'app', url: '/login', abstract: true, template: '<ui-view></ui-view>' }) $stateProvider.state('signin', { parent: 'login', url: '/signin', template: '<login-signin-directive></login-signin-directive>' }); $stateProvider.state('lock', { parent: 'login', url: '/lock', template: '<login-lock-directive></login-lock-directive>' }); $stateProvider.state('signup', { parent: 'login', url: '/signup', template: '<login-signup-directive></login-signup-directive>' }); $urlRouterProvider.rule(function ($injector, $location) { var UserService = $injector.get('UserService'); var path = $location.path(); if (!UserService.isLoggedIn() && path.indexOf('login') === -1) { $location.path('/login/signin'); } }); $urlRouterProvider.otherwise('/error/not-found'); } 

    () => { /* code */ }是ES6的语法,用来代替function() { /* code */ }

    首先,您需要一个服务,您可以注入到您的控制器,有一些应用程序身份验证的想法。 坚持使用本地存储的认证细节是一个体面的方法来处理它。

    接下来,您需要在状态更改之前检查权限的状态。 由于您的应用程序有一些需要进行身份验证的页面,而另一些则不需要创建一个父路由来检查身份验证,并使所有其他需要相同的页面成为该父项的子项。

    最后,您需要一些方法来判断您当前登录的用户是否可以执行某些操作。 这可以通过在auth服务中添加一个'can'来实现。 可以采用两个参数: – 操作 – 必需 – (即'manage_dashboards'或'create_new_dashboard') – 对象 – 可选 – 正在操作的对象。 例如,如果您有仪表板对象,则可能需要检查dashboard.ownerId === loggedInUser.id。 (当然,从客户端传递的信息永远不可信任,并且在将其写入数据库之前,您应该始终在服务器上进行验证)。

     angular.module('myApp', ['ngStorage']).config([ '$stateProvider', function( $stateProvider ) { $stateProvider .state('home', {...}) //not authed .state('sign-up', {...}) //not authed .state('login', {...}) //not authed .state('authed', {...}) //authed, make all authed states children .state('authed.dashboard', {...}) }]) .service('context', [ '$localStorage', function( $localStorage ) { var _user = $localStorage.get('user'); return { getUser: function() { return _user; }, authed: function() { return (_user !== null); }, // server should return some kind of token so the app // can continue to load authenticated content without having to // re-authenticate each time login: function() { return $http.post('/login.json').then(function(reply) { if (reply.authenticated === true) { $localStorage.set(_userKey, reply.user); } }); }, // this request should expire that token, rendering it useless // for requests outside of this session logout: function() { return $http.post('logout.json').then(function(reply) { if (reply.authenticated === true) { $localStorage.set(_userKey, reply.user); } }); }, can: function(action, object) { if (!this.authed()) { return false; } var user = this.getUser(); if (user && user.type === 'admin') { return true; } switch(action) { case 'manage_dashboards': return (user.type === 'manager'); } return false; } } }]) .controller('AuthCtrl', [ 'context', '$scope', function( context, $scope ) { $scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { //only require auth if we're moving to another authed page if (toState && toState.name.indexOf('authed') > -1) { requireAuth(); } }); function requireAuth() { if (!context.authed()) { $state.go('login'); } } }] 

    **免责声明:上面的代码是伪代码,并没有保证**

    使用$ http Interceptor

    通过使用$ http拦截器,您可以将标题发送到后端或其他方式,并以这种方式进行检查。

    关于$ http拦截器的大文章

    例:

     $httpProvider.interceptors.push(function ($q) { return { 'response': function (response) { // TODO Create check for user authentication. With every request send "headers" or do some other check return response; }, 'responseError': function (reject) { // Forbidden if(reject.status == 403) { console.log('This page is forbidden.'); window.location = '/'; // Unauthorized } else if(reject.status == 401) { console.log("You're not authorized to view this page."); window.location = '/'; } return $q.reject(reject); } }; }); 

    把它放在.config或.run函数中。