Каков правильный способ скрыть / показать AngularJS или любые другие компоненты пользовательского интерфейса одностраничного приложения на основе аутентифицированных прав пользователя?

#angularjs #spring-mvc #spring-security #authorization

#angularjs #spring-mvc #spring-безопасность #авторизация

Вопрос:

У меня есть приложение, которое использует Spring Security для аутентификации / авторизации на стороне сервера, Spring MVC для конечных точек на стороне сервера REST и AngularJS для просмотра.

На стороне сервера я реализовал все фильтры, необходимые для доступа ко всем этим конечным точкам REST, на основе прав пользователя. Мой вопрос в том, как мне следует подходить к созданию видимых / скрытых элементов html на основе аутентифицированных прав ПОЛЬЗОВАТЕЛЯ?

Например, у меня есть в представлении 3 кнопки (button1, button2, button3). Каждая кнопка имеет соответствующее ПРАВО ПОЛЬЗОВАТЕЛЯ, что должно сделать их видимыми / скрытыми. Давайте назовем эти права USER_RIGHT1, USER_RIGHT2, USER_RIGHT3.

Если у пользователя есть правильный USER_RIGHT1, он должен видеть в представлении button1, если у него есть правильный USER_RIGHT2, он должен видеть в представлении button2 и так далее.

Мой подход заключался в том, чтобы иметь список аутентифицированных прав пользователя в клиенте и сделать что-то в следующем примере:

 <div ng-if="rights contains USER_RIGHT1">
    <button name="button1".... />
</div>
<div ng-if="rights contains USER_RIGHT2">
    <button name="button2".... />
</div>
  

Я не уверен, должен ли список прав аутентифицированного пользователя быть в клиенте.

Как мне подойти к этой проблеме? Правильно ли я это делаю?

Ответ №1:

Мой подход в основном такой, какой вы предлагаете.

У вас может быть фабрика, которая хранит массив разрешений пользователя и имеет функцию для проверки наличия разрешения в массиве:

 .factory('Auth', function() {

var permissions = [];

return {
    allowed : function(permission) {

        return _.contains(permissions, permission);
    }
};});
  

Затем у вас может быть директива, которая показывает / скрывает элемент с помощью сервиса:

 .directive('allowed', function(Auth){

    return {

        link : function(scope, elem, attr) {

            if(!Auth.allowed(attr.allowed)){
                elem.hide();
            }
        }
    };
});
  

Итак, в ваших представлениях вам нужно только сделать:

 <div allowed="permission_name"> </div>
  

Комментарии:

1. Спасибо за ваш комментарий. Я нахожу этот подход очень простым, чистым и полезным.

2. Я не понимаю, почему это не является принятым решением.

Ответ №2:

Безопасность на клиенте, т. е. в браузере, практически бесполезна. Однако он должен присутствовать, чтобы обычный пользователь не мог видеть то, чего он не должен, однако сервер должен быть конечным местом, где обеспечивается безопасность.

Я бы создал быструю директиву для отображения / скрытия или компонентов пользовательского интерфейса и создал службу аутентификации для выполнения фактической логики, чтобы определить, имеет ли пользователь правильные права.

Я нахожусь на 60% пути, написав подробную статью об авторизации в AngularJS в своем блоге. Я бы проверил примерно через неделю, и я должен это сделать — это также может помочь вам с авторизацией маршрута.

ОБНОВЛЕНИЕ: сообщение в блоге об авторизации и безопасности angular route можно найти здесь

По сути, служба авторизации авторизует пользователя с помощью вашей серверной службы, а затем сохраняет их права на приложения.

Затем директива будет использовать эту службу, чтобы определить, имеет ли пользователь достаточно прав для просмотра компонента пользовательского интерфейса.

Я не тестировал приведенный ниже код, поэтому вам может потребоваться его отладка.

     angular.module('myApp').factory('authService', [
    function () {
        var loggedInUser,
            login = function (email, password) {
                //call server and rights are returned
                //loggedInUser is assigned
            },
            hasSecurityRoles = function (requiredRoles) {
                var hasRole = false,
                    roleCheckPassed = false,
                    loweredRoles;
                if (loggedInUser === undefined) {
                    hasRole = false;
                } else if (loggedInUser !== undefined amp;amp; requiredRoles === undefined) {
                    hasRole = true;
                } else if (loggedInUser !== undefined amp;amp; requiredRoles !== undefined) {
                    if (requiredRoles.length === 0) {
                        hasRole = true;
                    } else if (loggedInUser.permittedActions === undefined) {
                        hasRole = false;
                    } else {
                        loweredRoles = [];
                        angular.forEach(loggedInUser.permittedActions, function (role) {
                            loweredRoles.push(role.name.toLowerCase());
                        });

                        // check user has at least one role in given required roles
                        angular.forEach(requiredRoles, function (role) {
                            roleCheckPassed = roleCheckPassed || _.contains(loweredRoles, role.toLowerCase());
                        });

                        hasRole = roleCheckPassed;
                    }
                }

                return hasRole;
            };

        return {
            login: login,
            hasSecurityRoles: hasSecurityRoles
        };
    }
]);

    angular.module('myApp').directive('visibleToRoles', [
        'authService',
        function (authService) {
            return {
                link: function (scope, element, attrs) {
                    var makeVisible = function () {
                            element.removeClass('hidden');
                        },
                        makeHidden = function () {
                            element.addClass('hidden');
                        },
                        determineVisibility = function (resetFirst) {
                            if (resetFirst) {
                                makeVisible();
                            }

                            if (authService.hasSecurityRoles(roles)) {
                                makeVisible();
                            } else {
                                makeHidden();
                            }
                        },
                        roles = attrs.visibleToRoles.split(',');


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

Затем вы могли бы использовать его следующим образом

 <div visible-to-role="admin,usermanager">.....</div>
  

Комментарии:

1. Мне очень нравится этот подход, а также подход @cuttlas. Но использование этих прав, жестко запрограммированных в директиве visible-to-role, делает их по-прежнему видимыми для клиента (в инструментах разработки, таких как chrome). Можно ли что-то сделать в AngularJS, чтобы стереть эту директиву и ее параметры после ее оценки? Например, если я проверяю элементы в chrome в этом div, я действительно не хочу видеть жестко запрограммированный «admin, usermanager» (в моем примере я не хочу, чтобы клиенты проверяли права, необходимые им для выполнения определенных действий, или чтобы увидеть, что другие пользователи могут делать на этой странице)

2. А также мне действительно не нравится, что где-то в моем javascript есть список прав аутентифицированного пользователя. Даже если сервер не разрешает доступ к конечным точкам REST, пользователь может манипулировать этим объектом и предоставлять себе другие права. Я далеко думаю? Или я должен придерживаться простых вещей? 🙂

3. Однако, даже если вы удалите атрибут после того, как директива будет связана ‘el.removeAttr (‘visible-to-role’)’, я мог бы просто посмотреть ваш код javascript или, что еще лучше, изменить директиву. Так что вам действительно не следует думать, что что-либо за пределами вашего сервера в любом случае безопасно. Проще обмануть 99% вашей пользовательской базы — безопасность всегда должна выполняться на сервере.

4. Как описано ниже, многие из них вы можете использовать доступ на основе функций, а не доступ на основе ролей. Поэтому было бы сложно составить представление о том, кто что может делать.

Ответ №3:

Вместо того, чтобы иметь жестко запрограммированный список в ваших шаблонах / страницах, вы можете получить список прав аутентифицированного пользователя с сервера и загрузить его в свою область, а затем сделать то же самое, что вы делаете. Если вы используете ui-router, это возможно с помощью свойства resolve (то есть предварительной загрузки определенных данных, возможно, с сервера перед вызовом контроллера).

Таким образом, вы можете получить права только для пользователя, который просматривает страницу, а не жестко прописывать все права в клиенте.

Комментарии:

1. Спасибо за комментарий. Я думаю, что я не очень хорошо объяснил свой подход, но я делаю именно то, что вы говорите. Единственными жестко запрограммированными правами являются права, используемые в условиях ng-if, и я бы хотел, чтобы пользователь не мог видеть эти жестко запрограммированные права (например, если я открою dev-tools из chrome, я могу увидеть все эти жестко запрограммированные права…

2. Затем вы можете вместо отправки прав отправить что-то вроде «show_feature1», «show_feature2», где значения «show_xxx» устанавливаются на стороне сервера. Следовательно, то, что видит пользователь, намного более абстрактно, чем имя конкретного права. Кроме этого, я думаю, что ваш подход хорош.