首页 文章

使用AngularJS进行身份验证,使用REST Api WS进行会话管理和安全性问题

提问于
浏览
64

我开始使用angularJS开发一个web应用程序,我不确定一切都是正确的安全(客户端和服务器端) . 安全性基于单个登录页面,如果检查完凭证,我的服务器会发回一个具有自定义时间有效性的唯一令牌 . 所有其他REST api都可以通过此令牌访问 . 应用程序(客户端)浏览到我的入口点ex:https://www.example.com/home.html用户插入凭据并接收回一个唯一令牌 . 此唯一令牌使用AES或其他安全技术存储在服务器数据库中,不以明文格式存储 .

从现在开始,我的AngluarJS应用程序将使用此令牌对所有暴露的REST Api进行身份验证 .

我正在考虑将令牌临时存储在自定义的http cookie中;基本上,当服务器验证凭据时,它会发回一个新的Cookie Ex .

app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T

该cookie设置了secureHTTP Only标志 . Http协议直接管理新cookie并存储它 . 连续请求将使用新参数呈现cookie,而无需管理它并使用javascript存储它;在每次请求时,服务器使令牌无效并生成一个新令牌并将其发送回客户端 - >使用单个令牌防止重放攻击 .

当客户端从任何REST Api收到HTTP状态401未授权响应时,角度控制器清除所有cookie并将用户重定向到登录页面 .

我应该考虑其他方面吗?将令牌存储在新cookie或localStorage中更好吗?有关如何生成唯一强令牌的任何提示?

Edit (improvements):

  • 我决定使用HMAC-SHA256作为会话令牌生成器,有效期为20分钟 . 我生成一个随机的32字节GUID,附加一个时间戳并通过提供一个40字节的密钥来计算HASH-SHA256 . 由于令牌有效性非常小,因此很难获得冲突 .

  • Cookie将具有domain and path属性以提高安全性 .

  • 不允许多次登录 .

4 回答

  • 0

    如果您通过https与服务器通信,则重放攻击没有问题 .

    我的建议是利用您服务器的安全技术 . 例如,JavaEE具有开箱即用的登录机制,基于角色的声明性资源保护(您的REST endpoints )等 . 这些都是使用一组cookie管理的,您不必关心存储和过期 . 看看你的服务器/框架已经为你提供了什么 .

    如果您计划将API公开给更广泛的受众(不是专门针对您提供的基于浏览器的用户界面)或其他类型的客户(例如移动应用),请考虑采用OAuth .

    在我的头脑中,Angular具有以下安全功能(在弹出时会添加更多功能):

    CSRF / XSRF攻击

    Angular支持开箱即用的机制,以保护_2701587 . 看看 $http docs . 需要服务器端支持 .

    内容安全政策

    Angular具有一种表达式评估模式,该模式与启用CSP时强制执行的更严格的JavaScript运行时兼容 . 看看 ng-csp docs .

    严格的上下文转义

    使用Angular的新 $sce 功能(1.2)来强化您的UI以抵御XSS攻击等 . 它不太方便但更安全 . 看看文档here .

  • 9

    这是客户端安全性,您可以在常规Angular版本中实现 . 我试过并试过这个 . (请在此处找到我的文章: - http://www.codeproject.com/Tips/811782/AngularJS-Routing-Security)除了客户端路由安全性之外,您还需要在服务器端安全访问 . 客户端安全性有助于避免额外的服务器往返 . 但是,如果某人欺骗浏览器,那么服务器端安全性应该能够拒绝未经授权的访问 .

    希望这可以帮助!

    第1步:在app-module中定义全局变量

    • 定义应用程序的角色
    var roles = {
            superUser: 0,
            admin: 1,
            user: 2
        };
    
    • 针对应用程序的未授权访问的定义路由
    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]);
                         },
                     }
                 })
        });
    
  • 1
    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>
    
  • 52

    首先,你提出的问题没有简短或只有一个答案 . 除了已经回答的内容之外,让我尝试添加更多内容 . 在企业级,有四个主要组成部分,

    • UI

    • User Authentication Server - 在此验证用户凭据并为其生成必要的Cookie用户在UI上前进 . 如果此步骤失败,则用户会在此处停止 . 此服务器与API令牌生成无关,您也需要非基于API的系统.Google身份验证就是一个例子 .

    Extension:Siteminder Authentication

    SiteMinder Cookies, their Usage, Contents and Security

    Building a Java authentication server for Chatkit

    • API Token Server - 此服务器根据步骤2中生成的cookie生成API令牌,即您将cookie发送到服务器并获取令牌

    • APIs - 您使用步骤3中生成的令牌进行API调用 .

    您可以更好地独立部署和管理这四个组件,以实现更好的扩展 . 例如在这篇文章中,他们在单 endpoints 混淆了身份验证和令牌生成,这并不好 - Microservices with Spring Boot — Authentication with JWT (Part 3)

    通过你的写作,看起来你已经自己编写了第二部分和第三部分了 - 通常人们会使用一些现成的工具,如CA SiteMinder - How CA Siteminder works – Basics

    有关如何生成唯一强令牌的任何提示?

    我建议您通过标准化方式获得更好的可维护性和安全性,即您选择JWT格式 . JSON Web Token (JWT) Authentication Scheme

    您的令牌将被签名和加密,因此您还需要一个加密密钥服务器和一种机制来定期轮换这些密钥 .

    JSON Web Tokens - How to securely store the key?

    What is the difference between JWT and encrypting some json manually with AES?

    CA人员已在此社区门户网站上附上详细的pdf指南 - 这将有助于您了解整体流程 .

    Sample Code / App to use of REST JWT token API

    您的API代码需要获取加密密钥并解密和解码令牌以验证令牌 . 如果令牌被篡改或丢失,您需要标记它 . 有可用的库 .

    将令牌存储在新cookie或localStorage中更好吗?

    如果UI和API位于不同的域上,则为本地存储;如果位于同一域中,则为Cookie .

    Should JWT be stored in localStorage or cookie?

    Cross-Domain Cookies

    应用程序的安全性还取决于部署模型以及您未在问题中指定的部分 . 有时,开发人员可能会在代码中留下与SQL Injection一样简单的缺陷:)

    What if JWT is stolen?

相关问题