【问题标题】:Authentication with AngularJS, session management and security issues with REST Api WS使用 AngularJS 进行身份验证、使用 REST Api WS 进行会话管理和安全问题
【发布时间】:2014-01-19 03:49:36
【问题描述】:

我开始使用 angularJS 开发一个网络应用程序,但我不确定一切是否安全(客户端和服务器端)。 安全性基于单个登录页面,如果凭据检查正常,我的服务器会发回具有自定义时间有效性的唯一令牌。所有其他 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 中更好? 有关如何生成唯一强令牌的任何提示?

编辑(改进):

  • 我决定使用 HMAC-SHA256 作为会话令牌生成器,有效期为 20 分钟。我生成一个随机的 32 字节 GUID,附加时间戳并通过提供 40 字节密钥来计算 HASH-SHA256。由于令牌的有效性非常小,因此几乎不可能发生冲突。
  • Cookie 将具有domain and path 属性以提高安全性。
  • 不允许多次登录。

【问题讨论】:

  • 您似乎已经是,但只是为了让其他人清楚 - 始终使用 https 否则用户名/密码将作为纯文本发送。
  • 我有一个问题可能很简单。当您说客户端从 rest 接收到 401 的 HTTP 状态时,您正在清理并重定向到登录页面。因此,在您的代码中的某处,您将有一种 if 条件的 response.status 为 401。现在在调试模式下我们可以更改它,您如何处理这个?或者是否有任何黑客可以使用某些插件来更改 http 响应状态码的可能性?
  • 你可以在客户端做任何事情。您可以将 401 http 状态更改为 200 http 状态,然后呢?您可以对角度代码进行逆向工程并到达一个页面,该页面将向休息服务发出请求,该服务回复另一个 401 :) 最重要的是保护服务器端,并使攻击者很难或不可能使用假会话或没有会话。因此,我通过在每个休息 WS 上验证会话并仅在会话有效时才回复资源来处理它。

标签: angularjs rest authentication cookies angularjs-authentication


【解决方案1】:

如果你通过 https 与服务器对话,你不会遇到重放攻击的问题。

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

如果您打算将您的 API 公开给更广泛的受众(而不是专门针对您所服务的基于浏览器的 UI)或其他类型的客户端(例如移动应用程序),请考虑采用 OAuth。

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

CSRF/XSRF 攻击

Angular 支持开箱即用的CSRF 保护机制。查看$httpdocs。需要服务器端支持。

内容安全政策

Angular 有一种表达式评估模式,它与启用 CSP 时强制执行的更严格的 JavaScript 运行时兼容。查看ng-cspdocs

严格的上下文转义

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

【讨论】:

    【解决方案2】:

    这是您可以在常规 Angular 版本中实现的客户端安全性。 我已经尝试并测试了这一点。 (请在此处找到我的文章:-https://www.intellewings.com/post/authorizationonangularroutes) 除了客户端路由安全之外,您还需要保护服务器端的访问。 客户端安全有助于避免额外往返服务器。但是,如果有人欺骗了浏览器,那么服务器服务器端安全应该能够拒绝未经授权的访问。

    希望这会有所帮助!

    第 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]);
                         },
                     }
                 })
        });
    

    【讨论】:

    • 感谢分享这个片段。我试试看
    【解决方案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. 用户界面
      2. 用户身份验证服务器 - 您可以在此处验证用户凭据并生成必要的 cookie,以便用户在 UI 上继续前进。如果此步骤失败,用户将立即停止。此服务器与 API 令牌生成无关,您也需要它用于非基于 API 的系统。Google 身份验证就是一个例子。

      Extension:Siteminder Authentication

      SiteMinder Cookies, their Usage, Contents and Security

      Building a Java authentication server for Chatkit

      1. API 令牌服务器 - 此服务器根据第 2 步生成的 cookie 生成 API 令牌,即您将 cookie 发送到服务器并获取令牌
      2. APIs - 您使用第 3 步中生成的令牌进行 API 调用。

      最好独立部署和管理这四个组件以获得更好的规模。例如在本文中,他们在单端点混合了身份验证和令牌生成,这并不好 - Microservices with Spring Boot — Authentication with JWT (Part 3)

      根据您的记录,您似乎已经自己编写了组件 2 和 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 中还是更好? 本地存储?

      如果 UI 和 API 在不同的域中,则为本地存储;如果在同一域中,则为 Cookie。

      Should JWT be stored in localStorage or cookie?

      Cross-Domain Cookies

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

      What if JWT is stolen?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-22
        • 2013-06-19
        • 1970-01-01
        • 1970-01-01
        • 2015-01-30
        • 1970-01-01
        • 2013-01-05
        • 2018-08-27
        相关资源
        最近更新 更多