Аутентификация с помощью AngularJS, управление сеансом и проблемы безопасности с помощью REST Api WS
Я начал разрабатывать веб-приложение с angularJS, и я не уверен, что все правильно защищено (клиентская и серверная).
Безопасность основана на одной странице входа в систему, если учетные данные проверены в порядке, мой сервер отправляет уникальный токен с пользовательской временностью. Все остальные REST api доступны через этот токен.
Приложение (клиент) просматривает мою учетную запись ex: https://www.example.com/home.html пользователь вводит учетные данные и получает обратно уникальный токен. Этот уникальный токен хранится в базе данных сервера с помощью AES или других безопасных методов, он не сохраняется в ясном формате.
С этого момента мое приложение AngluarJS будет использовать этот токен для аутентификации для всех REST Api.
Я думаю о временном хранении токена в пользовательском http cookie; в основном, когда сервер проверяет учетные данные, он отправляет обратно новый файл cookie Ex.
app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T
В файле cookie установлены флаги secure и Только HTTP.
Протокол Http напрямую управляет новым файлом cookie и сохраняет его. Последовательные запросы будут содержать cookie с новым параметром, без необходимости управлять им и хранить его с помощью javascript; при каждом запросе сервер аннулирует токен и генерирует новый и отправляет его клиенту → предотвращать повторные атаки с помощью одного токена.
Когда клиент получает неавторизованный ответ HTTP-статуса 401 от любого REST Api, контроллер angular очищает все файлы cookie и перенаправляет пользователя на страницу входа.
Должен ли я рассмотреть другие аспекты? Лучше ли хранить токен в новом файле cookie или в localStorage?
Любые советы о том, как создать уникальный сильный токен?
Изменить (улучшения):
- Я решил использовать HMAC-SHA256 в качестве генератора токен сеанса с 20-минутной достоверностью. Я генерирую случайный 32-байтовый GUID, прикрепляю временную метку и вычисляю HASH-SHA256, предоставляя ключ 40 байтов. Совершенно невозможно получить столкновения, так как срок действия токена весьма минимален.
- Cookie будет иметь атрибуты домена и пути для повышения безопасности.
- Разрешено использование нескольких логинов.
Ответы
Ответ 1
Если вы разговариваете с сервером через https, у вас нет проблем с атаками повтора.
Мое предложение состояло в том, чтобы использовать технологию безопасности сервера. Например, JavaEE имеет встроенный механизм входа в систему, декларативную защиту ресурсов на основе ролей (конечные точки REST) и т.д. Все они управляются с помощью набора файлов cookie, и вам не нужно заботиться о хранении и истечения срока действия. Проверьте, что уже дает вам сервер/инфраструктура.
Если вы планируете распространять свой API на более широкую аудиторию (не специально на пользовательский интерфейс, основанный на браузере) или другие типы клиентов (например, мобильное приложение), подумайте о принятии OAuth.
Сверху моей головы Angular имеет следующие функции безопасности (добавит больше по мере их появления):
Атаки CSRF/XSRF
Angular поддерживает механизм CSRF. Проверьте $http
docs. Требуется поддержка на стороне сервера.
Политика безопасности контента
Angular имеет режим оценки выражения, который совместим с более строгими сценариями JavaScript, которые применяются, когда CSP включен. Проверьте ng-csp
docs.
Строгое контекстное экранирование
Используйте Angular новую функцию $sce
(1.2+), чтобы упростить вам интерфейс против атак XSS и т.д. Это немного менее удобно, но более безопасно. Ознакомьтесь с документами здесь.
Ответ 2
Это защита на стороне клиента, которую вы можете реализовать в обычных версиях Angular.
Я попробовал и проверил это.
(Пожалуйста, найдите мою статью здесь: - https://www.intellewings.com/post/authorizationonangularroutes)
В дополнение к безопасности маршрутов на стороне клиента, вам также необходимо защищать доступ на стороне сервера.
Безопасность на стороне клиента помогает избежать дополнительной поездки на сервер. Однако, если кто-то обманывает браузер, безопасность на стороне сервера должна быть в состоянии отклонить несанкционированный доступ.
Надеюсь, это поможет!
Шаг 1. Определите глобальные переменные в модуле приложения
-define роли для приложения
var roles = {
superUser: 0,
admin: 1,
user: 2
};
-define маршрут для несанкционированного доступа к приложению
var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';
Шаг 2. Определите сервис для авторизации
appModule.factory('authorizationService', function ($resource, $q, $rootScope, $location) {
return {
// We would cache the permission for the session, to avoid roundtrip to server for subsequent requests
permissionModel: { permission: {}, isPermissionLoaded: false },
permissionCheck: function (roleCollection) {
// we will return a promise .
var deferred = $q.defer();
//this is just to keep a pointer to parent scope from within promise scope.
var parentPointer = this;
//Checking if permisison object(list of roles for logged in user) is already filled from service
if (this.permissionModel.isPermissionLoaded) {
//Check if the current user has required role to access the route
this.getPermission(this.permissionModel, roleCollection, deferred);
} else {
//if permission is not obtained yet, we will get it from server.
// 'api/permissionService' is the path of server web service , used for this example.
$resource('/api/permissionService').get().$promise.then(function (response) {
//when server service responds then we will fill the permission object
parentPointer.permissionModel.permission = response;
//Indicator is set to true that permission object is filled and can be re-used for subsequent route request for the session of the user
parentPointer.permissionModel.isPermissionLoaded = true;
//Check if the current user has required role to access the route
parentPointer.getPermission(parentPointer.permissionModel, roleCollection, deferred);
}
);
}
return deferred.promise;
},
//Method to check if the current user has required role to access the route
//'permissionModel' has permission information obtained from server for current user
//'roleCollection' is the list of roles which are authorized to access route
//'deferred' is the object through which we shall resolve promise
getPermission: function (permissionModel, roleCollection, deferred) {
var ifPermissionPassed = false;
angular.forEach(roleCollection, function (role) {
switch (role) {
case roles.superUser:
if (permissionModel.permission.isSuperUser) {
ifPermissionPassed = true;
}
break;
case roles.admin:
if (permissionModel.permission.isAdministrator) {
ifPermissionPassed = true;
}
break;
case roles.user:
if (permissionModel.permission.isUser) {
ifPermissionPassed = true;
}
break;
default:
ifPermissionPassed = false;
}
});
if (!ifPermissionPassed) {
//If user does not have required access, we will route the user to unauthorized access page
$location.path(routeForUnauthorizedAccess);
//As there could be some delay when location change event happens, we will keep a watch on $locationChangeSuccess event
// and would resolve promise when this event occurs.
$rootScope.$on('$locationChangeSuccess', function (next, current) {
deferred.resolve();
});
} else {
deferred.resolve();
}
}
};
});
Шаг 3. Использование безопасности в маршрутизации. Позволяет использовать все наши жесткие слова, сделанные до сих пор, для защиты маршрутов
var appModule = angular.module("appModule", ['ngRoute', 'ngResource'])
.config(function ($routeProvider, $locationProvider) {
$routeProvider
.when('/superUserSpecificRoute', {
templateUrl: '/templates/superUser.html',//path of the view/template of route
caseInsensitiveMatch: true,
controller: 'superUserController',//angular controller which would be used for the route
resolve: {//Here we would use all the hardwork we have done above and make call to the authorization Service
//resolve is a great feature in angular, which ensures that a route controller(in this case superUserController ) is invoked for a route only after the promises mentioned under it are resolved.
permission: function(authorizationService, $route) {
return authorizationService.permissionCheck([roles.superUser]);
},
}
})
.when('/userSpecificRoute', {
templateUrl: '/templates/user.html',
caseInsensitiveMatch: true,
controller: 'userController',
resolve: {
permission: function (authorizationService, $route) {
return authorizationService.permissionCheck([roles.user]);
},
}
})
.when('/adminSpecificRoute', {
templateUrl: '/templates/admin.html',
caseInsensitiveMatch: true,
controller: 'adminController',
resolve: {
permission: function(authorizationService, $route) {
return authorizationService.permissionCheck([roles.admin]);
},
}
})
.when('/adminSuperUserSpecificRoute', {
templateUrl: '/templates/adminSuperUser.html',
caseInsensitiveMatch: true,
controller: 'adminSuperUserController',
resolve: {
permission: function(authorizationService, $route) {
return authorizationService.permissionCheck([roles.admin,roles.superUser]);
},
}
})
});
Ответ 3
app/js/app.js
-------------
'use strict';
// Declare app level module which depends on filters, and services
var app= angular.module('myApp', ['ngRoute']);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'loginCtrl'});
$routeProvider.when('/home', {templateUrl: 'partials/home.html', controller: 'homeCtrl'});
$routeProvider.otherwise({redirectTo: '/login'});
}]);
app.run(function($rootScope, $location, loginService){
var routespermission=['/home']; //route that require login
$rootScope.$on('$routeChangeStart', function(){
if( routespermission.indexOf($location.path()) !=-1)
{
var connected=loginService.islogged();
connected.then(function(msg){
if(!msg.data) $location.path('/login');
});
}
});
});
app/js/controller/loginCtrl.js
-------------------------------
'use strict';
app.controller('loginCtrl', ['$scope','loginService', function ($scope,loginService) {
$scope.msgtxt='';
$scope.login=function(data){
loginService.login(data,$scope); //call login service
};
}]);
app/js/directives/loginDrc.js
-----------------------------
'use strict';
app.directive('loginDirective',function(){
return{
templateUrl:'partials/tpl/login.tpl.html'
}
});
app/js/services/sessionService.js
---------------------------------
'use strict';
app.factory('sessionService', ['$http', function($http){
return{
set:function(key,value){
return sessionStorage.setItem(key,value);
},
get:function(key){
return sessionStorage.getItem(key);
},
destroy:function(key){
$http.post('data/destroy_session.php');
return sessionStorage.removeItem(key);
}
};
}])
app/js/services/loginService
----------------------------
'use strict';
app.factory('loginService',function($http, $location, sessionService){
return{
login:function(data,scope){
var $promise=$http.post('data/user.php',data); //send data to user.php
$promise.then(function(msg){
var uid=msg.data;
if(uid){
//scope.msgtxt='Correct information';
sessionService.set('uid',uid);
$location.path('/home');
}
else {
scope.msgtxt='incorrect information';
$location.path('/login');
}
});
},
logout:function(){
sessionService.destroy('uid');
$location.path('/login');
},
islogged:function(){
var $checkSessionServer=$http.post('data/check_session.php');
return $checkSessionServer;
/*
if(sessionService.get('user')) return true;
else return false;
*/
}
}
});
index.html
----------
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
<div ng-view></div>
<!-- In production use:
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
-->
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/directives/loginDrc.js"></script>
<script src="js/controllers/loginCtrl.js"></script>
<script src="js/controllers/homeCtrl.js"></script>
<script src="js/services/loginService.js"></script>
<script src="js/services/sessionService.js"></script>
</body>
</html>
Ответ 4
Во-первых, нет короткого или только одного ответа на то, что вы спросили. В дополнение к тому, что уже было дано, позвольте мне добавить еще кое-что. На уровне предприятия существует четыре основных компонента:
- UI
-
Сервер аутентификации пользователей - здесь вы проверяете учетные данные пользователя и генерируете необходимые файлы cookie, чтобы пользователь мог двигаться дальше по пользовательскому интерфейсу. Если этот шаг не выполняется, пользователь сразу же останавливается. Этот сервер не имеет никакого отношения к генерации токенов API & это необходимо и для систем, не основанных на API. Один из примеров - аутентификация Google.
Расширение: аутентификация Siteminder
Файлы cookie SiteMinder, их использование, содержание и безопасность
Создание сервера аутентификации Java для Chatkit
- Сервер токенов API - этот сервер генерирует токены API на основе файлов cookie, созданных на шаге №2, т.е. вы отправляете файлы cookie на сервер и получаете токен
- .API - вы используете токен, сгенерированный на шаге 3, для выполнения вызовов API.
Лучше, если вы развернете & Управляйте этими четырьмя компонентами независимо для лучшего масштаба. например в этой статье они перепутали аутентификацию & генерация токенов в одной конечной точке & это не хорошо - Микросервисы с Spring Boot - Аутентификация с помощью JWT (часть 3)
По вашей записи, похоже, что вы написали второй компонент & три самостоятельно - обычно люди используют для этого несколько готовых инструментов, таких как CA SiteMinder - Как работает CA Siteminder - Основы
Какие-нибудь советы о том, как создать уникальный сильный токен?
Я бы посоветовал вам пройти стандартизированный путь для лучшей ремонтопригодности & безопасность, т.е. вы выбираете формат JWT. Схема аутентификации JSON Web Token (JWT)
Ваш токен будет подписан & зашифрован, так что вам также понадобится сервер ключей шифрования & механизм для вращения этих клавиш через равные промежутки времени.
JSON Web Tokens - Как надежно хранить ключ?
В чем разница между JWT и шифрованием некоторых json вручную с помощью AES?
Сотрудник центра сертификации приложил подробное руководство в формате PDF на этом портале сообщества, которое поможет вам понять общий ход.
Пример кода/приложения для использования API REST JWT token
Ваш код API должен будет получить ключ шифрования и расшифровать & декодировать токен для аутентификации токена. Если токен поврежден или отсутствует, необходимо пометить его как таковой. Для этого есть библиотеки.
Лучше хранить токен внутри нового файла cookie или в LocalStorage?
локальное хранилище, если пользовательский интерфейс & API находятся в разных доменах & Печенье, если на том же домене.
Должен ли JWT храниться в localStorage или cookie?
Междоменные файлы cookie
Безопасность приложения также зависит от модели развертывания и той части, которую вы не указали в своем вопросе. Иногда разработчики могут оставить такие же недостатки в своем коде, как SQL-инъекция :)
Что делать, если JWT украден?