
В какой-то момент приложению требуется авторизация. Это означает, что разные уровни доступа ведут себя по-разному на веб-сайте (или в чем угодно еще). Это может быть что угодно - от просмотра данных до целых областей, которые недоступны группе пользователей.
В приложениях, не являющихся одностраничными (SPA), утверждение или роль связаны с данными или областью приложения - либо у пользователя есть эта роль или утверждение, либо нет. В SPA все то же самое, но с огромной оговоркой. SPA загружается в браузер. В этот момент браузер получает полный контроль над кодом. Злоумышленник может изменить код для своих целей.
Поскольку SPA не могут быть защищены, аутентификация и авторизация в SPA - это просто пользовательский опыт. Вся значимая безопасность должна обеспечиваться на веб-сервере. Эта статья не охватывает защиту вашего API от атак. Я рекомендую посмотреть видео от Pluralsight или прочитать статью, посвященную безопасности для вашей серверной технологии.
Цель этой статьи - показать вам, как я добавил пользовательский опыт авторизации в мое Angular 1.x SPA.
Области безопасности
Я выделил 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.
});
Как я упоминал выше, это не заменяет защиту сервера. Этот код работает для обеспечения замечательного пользовательского опыта.
Автор: Чак Конвей специализируется на разработке программного обеспечения и генеративном ИИ. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube.