Аутентификация с помощью 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

Во-первых, нет короткого или только одного ответа на то, что вы спросили. В дополнение к тому, что уже было дано, позвольте мне добавить еще кое-что. На уровне предприятия существует четыре основных компонента:

  1. UI
  2. Сервер аутентификации пользователей - здесь вы проверяете учетные данные пользователя и генерируете необходимые файлы cookie, чтобы пользователь мог двигаться дальше по пользовательскому интерфейсу. Если этот шаг не выполняется, пользователь сразу же останавливается. Этот сервер не имеет никакого отношения к генерации токенов API & это необходимо и для систем, не основанных на API. Один из примеров - аутентификация Google.

Расширение: аутентификация Siteminder

Файлы cookie SiteMinder, их использование, содержание и безопасность

Создание сервера аутентификации Java для Chatkit

  1. Сервер токенов API - этот сервер генерирует токены API на основе файлов cookie, созданных на шаге №2, т.е. вы отправляете файлы cookie на сервер и получаете токен
  2. .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 украден?