Angular 具有多个角色的 SPA

Angular SPA with multiple roles

我正在使用 Angular 1.5 构建 SPA,其中用户具有多个角色 [管理员、'see everything'、'edit specific stuff'、基本用户]。我花了一整天的时间在谷歌上搜索,偶然发现了很多不同的解决方案,其中大多数似乎已经过时了。我的后台是Slim应用,使用token认证。

我的问题:如何为每个角色实现以不同方式显示相同的 ng-view?登录后应该后端 return 我的角色和令牌吗?当用户尝试更改位置时,如何检查他是否已登录?我假设仅检查令牌是否存储在客户端上是不够的,因为我无法知道令牌何时过期?

我对基于令牌的身份验证和 Angular 真的很陌生。我不了解在我的案例中应该如何处理前端,而且我不知道从哪里开始。如果有任何答案、建议和提示可以帮助我理解应该做什么以及应该如何做,我将不胜感激。谢谢。

请检查

http://arthur.gonigberg.com/2013/06/29/angularjs-role-based-auth/

使用解析使任何自定义函数在路由中被检查

$routeProvider.when('/应用程序', { 模板网址:'app.html', 控制器:'AppController' 解决: { //... } });

您可以在此处找到使用 resolve 的示例

http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx

首先,使用 UI-Router 来管理您的状态,即 urls.

登录机制

成功登录后,您的后端必须 return 一个访问令牌,但可能会也可能不会 return 与它一起发挥作用。但是您始终可以发送另一个请求来获取用户数据和角色并将它们保存到您的本地存储中。您可以创建服务,例如LoggedInUser 获取用户信息和角色。

angular
  .module('myApp')
  .factory('LoggedInUser', [
    '$q',
    'AppConfig',
    'User',
    function ($q,
              AppConfig,
              User) {

      function getUser() {
        var defer = $q.defer();
        var userId = localStorage.getItem('$MyApp$userId');
        var user = null;

        var userStringified = localStorage.getItem('$MyApp$currentUser');
        try {
          user = JSON.parse(userStringified);
        } catch (error) {
          defer.reject(error);
        }

        if (user) {
          defer.resolve(user);
        } else {
          User.findById({ // This is a server query
            id: userId,
            filter: {
              include: 'roles'
            }
          })
            .$promise
            .then(function (userData) {
              localStorage.setItem('$MyApp$currentUser', JSON.stringify(userData));
              defer.resolve(userData);
            }, function (error) {
              defer.reject(error);
            });
        }

        return defer.promise;
      }

      function clearUser() {
          localStorage.removeItem('$MyApp$currentUser');
      }

      return {
        getUser: getUser,
        clearUser: clearUser
      }

    }]);

令牌到期

您可以使用 Angular Interceptors 拦截具有特定状态代码的服务器响应或您可以从服务器代码添加的任何自定义字段,例如code: 'TOKEN_EXPIRED' 在响应对象上,然后采取一些操作,例如注销用户或从拦截器发送另一个请求以获取另一个令牌以继续操作。

这取决于您的请求的重要性。在我的例子中,我可以显示一条消息,注销用户。

多个角色

处理多个角色对于前端和后端有不同的含义。

1。前端

在前端,限制分两层

1.1。整个视图

您可能希望根据特定条件阻止状态激活,例如

  • 忘记密码页面应该只对未登录的用户可见,如果任何登录用户试图通过键入 url 访问,它应该重定向到他们的主页或仪表板页面。
  • 仪表板页面应该对具有任何角色的登录用户可见。
  • 用户列表页面应该对具有管理员角色的登录用户可见。

为了有这个限制,你需要 2 个数据项,一个是检查状态是否需要身份验证,另一个是检查该状态允许的角色。我正在使用 authenticate 具有 true/falseallowedRoles 具有一系列允许的角色。然后您可以在 $stateChangeStart 事件中使用它们。这是代码:

在您配置状态的一些 js 文件中:

$stateProvider
    .state('home', {
      abstract: true,
      url: '',
      templateUrl: 'app/home/home.html',
      controller: 'HomeController',
      resolve: {
        loggedInUser: [
          'LoggedInUser',
          '$window',
          '$q',
          'User',
          function (LoggedInUser,
                    $window,
                    $q,
                    User) {
            var defer = $q.defer();
            LoggedInUser.getUser()
              .then(function (user) {
                defer.resolve(user);
              }, function (error) {
                $window.alert('User is not authenticated.');
                defer.reject(error);
              });

            return defer.promise;
          }
        ]
      },
      data: {
        authenticate: true
      }
    })
    .state('home.dashboard', {
      url: '/dashboard',
      templateUrl: 'app/dashboard/dashboard.html',
      controller: 'DashboardController',
      data: {
        roles: ['admin', 'basic-user']
      }
    })
   .state('home.list-users', {
      url: '/list-users',
      templateUrl: 'app/users/list/list-users.html',
      controller: 'ListUsersController',
      data: {
        roles: ['admin']
      }
    })
    // More states

app.jsrun

$rootScope.$on('$stateChangeStart', function (event, next, nextParams) {
    var userLoggedIn = User.isAuthenticated();
    /* If next state needs authentication and user is not logged in
     * then redirect to login page.
     */
    var authenticationRequired = (next.data && next.data.authenticate);
    if (authenticationRequired) {
      if (userLoggedIn) {
       // Check role of logged in user and allowed roles of state to see if state is protected
        var allowedRoles = next.data && next.data.roles;
        if (allowedRoles && allowedRoles.length > 0) {
          console.log('State access allowed to: ' + allowedRoles);
          LoggedInUser.getUser()
            .then(function (result) {
              var allowed = false;
              var user = result;
              var role = user.role;
              var allowed = (allowedRoles.indexOf(role) >= 0);
              console.log(role + ' can access ' + allowed);

              if (!allowed) {
                var isAdministrator = user.isAdministrator;
                if (isAdministrator) {
                  console.log('User is administrator: ' + isAdministrator);
                  role = 'administrator';
                  allowed = (allowedRoles.indexOf(role) >= 0);
                  console.log(role + ' can access ' + allowed);
                }
              }

              if (!allowed) {
                event.preventDefault(); //prevent current page from loading
                $state.go('home.dashboard');
                AppModalBox.show('danger', 'Not Authorized', 'You are not authorized to view this page.');
              }
            }, function (error) {
              console.log(error);
            });
        }
      } else {
        event.preventDefault(); // Prevent current page from loading
        $state.go('publicHome.auth.login');
      }
    } else {
      /* This code block handles publicly accessible page like login, forgot password etc.
       * Public states (pages) have redirectToLoggedInState data set to either true or false.
       * redirectToLoggedInState set to true/false means that public page cannot/can be accessed by the logged in user.
       *
       * If user is logged in and any public state having redirectToLoggedInState data set to true
       * then redirect to dashboard page and prevent opening page.
       */
      var redirectToLoggedInState = (next.data && next.data.redirectToLoggedInState);
      if (userLoggedIn && redirectToLoggedInState) {
        event.preventDefault(); //prevent current page from loading
        $state.go('home.dashboard');
      }
    }
  });

Data defined on super state is available on child states so you can group all your private states (which needs authentication) under one state (In the above example, it is home) and put data.authenticate on super most state.

1.2。到视图中的某些部分

为了有这个限制,你可以在你的controller中使用相同的LoggedInUser服务或者在super most状态下使用解析loggedInUser。将角色附加到 $scope 并使用 ng-if 显示和隐藏基于角色的部分。

Resolves defined on the super most state are accessible in all child states.

代码如下:

angular.module('myApp')
  .controller('DashboardController', [
    '$scope',
    'loggedInUser',
    function ($scope,
              loggedInUser) {

      $scope.roles = loggedInUser.roles;

    }]);

2。后端

您可以从 localStorage 获取访问令牌,您必须在成功登录时保存它,并在每个请求中传递它。在后端,解析请求以检索访问令牌,获取此访问令牌的用户 ID,获取用户的角色,并检查是否允许具有这些角色的用户执行操作(如查询)。

我正在使用 NodeJS - ExpressJS,其中中间件用于处理此类基于角色的身份验证。