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 专注于软件工程和生成式人工智能。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTube

↑ 回到顶部

您可能还喜欢