Skip to content

文章

使用声明保护 AngularJS

2015年2月14日 • 7 分钟阅读

使用声明保护 AngularJS

在某个时刻,应用程序需要授权。这意味着不同级别的访问在网站上表现不同(或任何其他情况)。它可以是从查看数据到整个区域都无法被一组用户访问的任何内容。

在非单页应用程序(SPA)中,声明或角色与应用程序的数据或区域相关联,用户要么拥有此角色或声明,要么没有。在 SPA 中也是如此,但有一个巨大的免责声明。SPA 被下载到浏览器。此时浏览器对代码拥有完全控制权。恶意人士可以更改代码来为其所用。

由于 SPA 无法被保护,SPA 中的身份验证和授权只是用户体验。所有有意义的安全性必须在 Web 服务器上完成。本文不涉及保护您的 API 免受攻击。我建议观看 Pluralsight 的视频或阅读涉及服务器技术安全性的论文。

本文的目的是向您展示我如何为 Angular 1.x SPA 添加授权用户体验。

安全范围

我已确定需要授权的 UI 的 3 个区域:元素(HTML)路由数据

提醒一下,保护 SPA 不能替代保护服务器。客户端上的权限只是为了让诚实的人保持诚实,并为用户提供良好的体验。

3 个区域的详细信息:

元素

您需要隐藏特定的 HTML 元素。它可以是标签、包含数据的表格、按钮或页面上的任何元素。

路由

您需要隐藏整个路由。在某些情况下,您不希望用户访问视图。通过保护路由,用户无法导航到该视图。相反,他们会看到”您无权导航到此视图”的消息。

数据

有时隐藏视图中的元素是不够的。精明的用户可以简单地查看源代码并在 HTML 源代码中看到隐藏的数据或观察其流向浏览器。我们想要的是首先不检索数据。

添加安全性很棘手。起初我尝试在 HTTP API(在客户端)处约束访问。我很快意识到这行不通。用户可能没有直接访问数据的权限,但这并不意味着他们没有间接访问数据的权限。在 HTTP API 层(通常是应用程序中最低的之一),我们无法判断调用的上下文,因此无法对其应用安全问题。

下面我提供了代码示例:

代码

我为授权检查代码创建了一个服务。这是授权的核心。所有授权请求都使用此服务来检查用户是否被授权执行特定操作。

angular.module('services')
    .service('AuthorizationContext',function(_, Session){

        this.authorizedExecution = function(key, action){

            //Looking for the claim key that was passed in. If it exists in the claim set, then execute the action.
            Session.claims(function(claims){
                var claim = findKey(key, claims);

                //If Claim was found then execute the call.
                //If it was not found, do nothing
                if(claim !== undefined){
                    action();
                }
            });
        };

        this.authorized = function(key, callback){
            //Looking for the claim key that was passed in. If it exists in the claim set, then execute the action.
            Session.claims(function(claims){
                var claim = findKey(key, claims);

                //If they don't have any security key, then move forward and authorization.
                var valid = claim !== undefined;
                callback(valid);
            });
        };

        //this.agencyViewKey = '401D91E7-6EA0-46B4-9A10-530E3483CE15';

        function findKey(key, claims){
            var claim = _.find(claims, function(item){
                return item.value === key;
            });

            return claim;
        }
    });

授权指令

授权指令可以应用于您想要从没有特定访问级别的用户隐藏的任何 HTML 元素。如果用户拥有作为其声明一部分的访问令牌,则允许他们查看该元素。如果没有,则从他们隐藏。

angular.module('directives')
    .directive('authorize', ['$compile', 'AuthorizationContext', function($compile, AuthorizationContext) {
        return {
            restrict: 'A',
            replace: true,
            //can't have isolated the scope in a shared directive
            link:function ($scope, element, attributes) {

                var securityKey = attributes.authorize;
                AuthorizationContext.authorized(securityKey, function(authorized){
                    var el = angular.element(element);
                    el.attr('ng-show', authorized);

                    //remove the attribute, otherwise it creates an infinite loop.
                    el.removeAttr('authorize');
                    $compile(el)($scope);
                });
            }
        };
    }]);

元素

我在应用程序中大量依赖选项卡。我将授权指令应用于我想要从没有适当声明的用户隐藏的选项卡。

<tabset>
<tab ng-cloak heading="Users" authorize="{{allowUserManagement}}">
...html content
</tab>
</tabset>

路由

我使用的是 ui-router。不幸的是,对于那些不使用的人,我没有开箱即用的 AngularJS 路由器的代码。

$stateChangeStart 中,我对路由进行身份验证。这是该事件中的代码。

$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
   AuthenticationManager.authenticate(event, toState, toParams);
});

授权路由的函数。如果已授权,则允许路由继续。如果未授权,则向用户显示消息,并将其定向到主页。

function authorizedRoute(toState, location, toaster, breadCrumbs){
   if(toState.authorization !== undefined){
       AuthorizationContext.authorized(toState.authorization, function(authorized){
           if(!authorized){
               toaster.pop('error', 'Error', 'You are not authorized to view this page.');
               location.path("/search");
           } else {
               breadCrumbs();
           }
       });
   } else{
       breadCrumbs();
   }
}

在此路由定义中,您会注意到一个名为”authorization”的属性。如果用户拥有此声明,则允许他们继续。

angular.module('agency',
    [
        'ui.router',
        'services'
    ])
    .config(function config($stateProvider){
    $stateProvider.state( 'agency', {
        url: '/agency',
        controller: 'agency.index',
        templateUrl: 'agency/agency.tpl.html',
        authenticate: true,
        authorization:'401d91e7-6ea0-46b4-9a10-530e3483ce15',
        data:{ pageTitle: 'Agency' }
    });
});

数据

在某些情况下,您不想向服务器请求数据。如果用户拥有该声明,则允许他们发出请求。

上面文章开头的 AuthorizationContext 显示了 authoriedExecution 的代码。这里您看到它的用法。

AuthorizationContext.authorizedExecution(Keys.authorization.allowUserManagement, function(){
    //execute code, if the loggedin user has rights.

                });

如上所述,这不能替代保护服务器。此代码用于提供良好的用户体验。

作者:Chuck Conway 是一位 AI 工程师,拥有近 30 年的软件工程经验。他构建实用的 AI 系统——内容管道、基础设施代理和解决实际问题的工具——并分享他沿途的学习成果。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTubeSubStack

↑ 返回顶部

你可能也喜欢