Angular+RESTful Client-side Communication w/ API for Auth/(re)Routing

这已经在几个不同的问题和几个不同的教程中讨论过了,但我之前遇到的所有资源都不是一针见血的.

在坚果壳里,我需要

  • 通过POST从http://client.foo登录到http://api.foo/login
  • 为用户提供"已登录"的GUI/组件状态,以提供logout个路由
  • 能够在用户注销/注销时"更新"UI.
  • 保护我的路由以判断身份验证状态(如果他们需要),并相应地将用户重定向到登录页面

我的问题是

  • 每次我导航到不同的页面时,我都需要拨打api.foo/status以确定用户是否登录.(我用特快专递取款机取款)这会导致打嗝,比如ng-show="user.is_authenticated"
  • 当我成功登录/注销时,我需要刷新页面(我不想这样做),以便填充{{user.first_name}}之类的内容,或者在注销的情况下,清空该值.
// Sample response from `/status` if successful 

{
   customer: {...},
   is_authenticated: true,
   authentication_timeout: 1376959033,
   ...
}

我试过的

为什么我觉得我疯了

  • 似乎每个教程都依赖于某种数据库(很多Mongo、Coach、PHP+MySQL、ad infinitum)解决方案,而没有一个教程完全依赖于与RESTful API的通信来保持登录状态.登录后,会发送withCredentials:true条额外的帖子/获取,所以这不是问题所在
  • 我找不到任何可以使用Angular+REST+Auth、无后端语言的示例/教程/repo.

我不太骄傲

诚然,我对棱角分明是个新手,如果我以一种荒谬的方式来处理这个问题,我也不会感到惊讶;如果有人建议一个替代方案,我会很激动,即使它是汤到坚果.

我之所以使用Express,主要是因为我真的很喜欢JadeStylus——我不喜欢Express的路由,如果我想做的只有Angular的路由,我会放弃它.

提前感谢任何人提供的帮助.请不要让我用谷歌搜索,因为我有大约26页的紫色链接


1此解决方案依赖Angular的$httpBackend mock,目前尚不清楚如何使其与真正的服务器对话.

2这是最接近的,但由于我有一个需要验证的现有API,我无法使用passport的"localStrategy",而且似乎要编写一个OAUTH服务...只有我想用的.

推荐答案

这篇文章摘self 关于url路由授权和元素安全here的博客文章,但我将简要总结要点:-)

Security in frontend web application is merely a starting measure to stop Joe Public, however any user with some web knowledge can circumvent it so you should always have security server-side as well.

angular中有关安全性的主要问题是路由安全,幸运的是,在angular中定义路由时,您正在创建一个对象,一个可以具有其他属性的对象.我的方法的基础是在这个路由对象中添加一个安全对象,它基本上定义了用户访问特定路由所必须的角色.

 // route which requires the user to be logged in and have the 'Admin' or 'UserManager' permission
    $routeProvider.when('/admin/users', {
        controller: 'userListCtrl',
        templateUrl: 'js/modules/admin/html/users.tmpl.html',
        access: {
            requiresLogin: true,
            requiredPermissions: ['Admin', 'UserManager'],
            permissionType: 'AtLeastOne'
        });

整个方法的重点是授权服务,它基本上是判断用户是否拥有所需的权限.该服务将问题从该解决方案的其他部分抽象出来,与用户及其在登录期间从服务器检索到的实际权限有关.虽然代码非常冗长,但在我的博客文章中有详细的解释.然而,它基本上处理许可判断和两种授权模式.第一个是用户必须至少拥有其中一个已定义的权限,第二个是用户必须拥有所有已定义的权限.

angular.module(jcs.modules.auth.name).factory(jcs.modules.auth.services.authorization, [  
'authentication',  
function (authentication) {  
 var authorize = function (loginRequired, requiredPermissions, permissionCheckType) {
    var result = jcs.modules.auth.enums.authorised.authorised,
        user = authentication.getCurrentLoginUser(),
        loweredPermissions = [],
        hasPermission = true,
        permission, i;

    permissionCheckType = permissionCheckType || jcs.modules.auth.enums.permissionCheckType.atLeastOne;
    if (loginRequired === true && user === undefined) {
        result = jcs.modules.auth.enums.authorised.loginRequired;
    } else if ((loginRequired === true && user !== undefined) &&
        (requiredPermissions === undefined || requiredPermissions.length === 0)) {
        // Login is required but no specific permissions are specified.
        result = jcs.modules.auth.enums.authorised.authorised;
    } else if (requiredPermissions) {
        loweredPermissions = [];
        angular.forEach(user.permissions, function (permission) {
            loweredPermissions.push(permission.toLowerCase());
        });

        for (i = 0; i < requiredPermissions.length; i += 1) {
            permission = requiredPermissions[i].toLowerCase();

            if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.combinationRequired) {
                hasPermission = hasPermission && loweredPermissions.indexOf(permission) > -1;
                // if all the permissions are required and hasPermission is false there is no point carrying on
                if (hasPermission === false) {
                    break;
                }
            } else if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.atLeastOne) {
                hasPermission = loweredPermissions.indexOf(permission) > -1;
                // if we only need one of the permissions and we have it there is no point carrying on
                if (hasPermission) {
                    break;
                }
            }
        }

        result = hasPermission ?
                 jcs.modules.auth.enums.authorised.authorised :
                 jcs.modules.auth.enums.authorised.notAuthorised;
    }

    return result;
};

既然路由具有安全性,您需要一种方法来确定当路由更改开始时,用户是否可以访问该路由.为此,我们将拦截路由更改请求,判断路由对象(上面有我们的新访问对象),如果用户无法访问视图,我们将用另一个替换路由.

angular.module(jcs.modules.auth.name).run([  
    '$rootScope',
    '$location',
    jcs.modules.auth.services.authorization,
    function ($rootScope, $location, authorization) {
        $rootScope.$on('$routeChangeStart', function (event, next) {
            var authorised;
            if (next.access !== undefined) {
                authorised = authorization.authorize(next.access.loginRequired,
                                                     next.access.permissions,
                                                     next.access.permissionCheckType);
                if (authorised === jcs.modules.auth.enums.authorised.loginRequired) {
                    $location.path(jcs.modules.auth.routes.login);
                } else if (authorised === jcs.modules.auth.enums.authorised.notAuthorised) {
                    $location.path(jcs.modules.auth.routes.notAuthorised).replace();
                }
            }
        });
    }]);

这里的关键真的是"安全".replace()'这会将当前路由(他们无权查看的路由)替换为我们将其重定向到的路由.这一站任何人都可以回到未经授权的路由.

现在我们可以拦截路由,我们可以做很多很酷的事情,包括如果用户登陆到他们需要登录的路由上,我们可以做redirecting after a login件事.

解决方案的第二部分是能够根据用户的权限向用户隐藏/显示UI元素.这是通过一个简单的指令实现的.

angular.module(jcs.modules.auth.name).directive('access', [  
        jcs.modules.auth.services.authorization,
        function (authorization) {
            return {
              restrict: 'A',
              link: function (scope, element, attrs) {
                  var makeVisible = function () {
                          element.removeClass('hidden');
                      },
                      makeHidden = function () {
                          element.addClass('hidden');
                      },
                      determineVisibility = function (resetFirst) {
                          var result;
                          if (resetFirst) {
                              makeVisible();
                          }

                          result = authorization.authorize(true, roles, attrs.accessPermissionType);
                          if (result === jcs.modules.auth.enums.authorised.authorised) {
                              makeVisible();
                          } else {
                              makeHidden();
                          }
                      },
                      roles = attrs.access.split(',');


                  if (roles.length > 0) {
                      determineVisibility(true);
                  }
              }
            };
        }]);

然后,您可以确定这样一个元素:

 <button type="button" access="CanEditUser, Admin" access-permission-type="AtLeastOne">Save User</button>

阅读我的full blog post篇文章,了解该方法更详细的概述.

Node.js相关问答推荐

monorepo内的NPM包使用不在注册表中的本地包

查询嵌套数组中的最后一个元素具有特定值的mongoDB集合中的文档

Firebase-admin筛选器.或只考虑第一个WHERE子句

如何在RavenDB中执行JS索引?

Sequelize-测试使用虚拟场更新模型

Webpack:如何避免导出函数的重命名?

创建查询以展开数组并筛选出具有特定值的元素之后的元素

将图像添加到多个产品的条带 checkout 会话中

仅在 vue 脚本未退出的情况下使用 docker 时出现错误

WSL2 上需要脚本运行的 NPM 包的权限被拒绝

Azure Function 在 2022 年 4 月 26 日禁用将用户字段设置为 HTTP 请求对象

等到文件上传完成的有效方法(mongoose )

node.js 是否支持yields ?

在 Node.js 中的两个不同进程之间进行通信

如何在 NodeJs 中下载和解压缩内存中的 zip 文件?

为什么 Node 控制台不显示功能代码?

如何从命令行在 Node.js 上运行 Jasmine 测试

Passport 的 req.isAuthenticated 总是返回 false,即使我硬编码 done(null, true)

node --experimental-modules,请求的模块不提供名为的导出

node.js 找不到模块mongodb