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


68

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

从现在开始,我的AngluarJS应用将使用此令牌对所有公开的REST Api进行身份验证。

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

app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T

cookie的安全HTTP Only标志设置为打开。Http协议直接管理新的cookie并将其存储。连续的请求将向cookie提供带有新参数的cookie,而无需对其进行管理并使用javascript进行存储;在每次请求时,服务器都会使令牌无效并生成一个新令牌,然后将其发送回客户端->防止使用单个令牌进行重放攻击。

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

我是否应该考虑其他方面?将令牌存储在新的cookie或localStorage中更好吗?关于如何生成独特的强令牌的任何技巧?

编辑(改进):

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

2
您似乎已经可以了,但只是想让其他人明白-始终使用https,否则用户名/密码将以纯文本格式发送。
foiseworth,2014年

1
我有一个问题可能很简单。当您说客户端从休息状态接收到HTTP状态401时,您正在清理并重定向到登录页面。因此,在代码中的某处,response.status的条件条件为401。现在在调试模式下,我们可以对其进行更改,您将如何处理?还是任何黑客都可以使用某些插件来更改http响应状态代码?
paramupk 2015年

1
您可以在客户端执行任何操作。您可以将401 http状态更改为200 http状态吗?您可以对角度代码进行逆向工程,然后到达一个页面,该页面将向REST服务请求并返回另一个401 :)最重要的是确保服务器端的安全,并使攻击者很难或不可能使用WS来调用REST WS。假会话或没有会话。因此,我通过在每个其余WS上验证会话来处理它,并仅在会话有效时才使用资源进行回复。
StarsSky

Answers:


55

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

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

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

Angular具有以下安全功能(弹出时会添加更多功能):

CSRF / XSRF攻击

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

内容安全政策

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

严格的上下文转义

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


10

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

希望这可以帮助!

步骤1:在应用程序模块中定义全局变量

-定义应用程序的角色

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

感谢您分享此内容。我会尝试一下
Awena 2014年

2
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>

2

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

  1. 用户界面
  2. 用户身份验证服务器-在这里,您可以验证用户凭据并生成必要的Cookie,以便用户在UI上继续前进。如果此步骤失败,则用户将在那里停下来。该服务器与API令牌生成无关,对于非基于API的系统,您也需要此服务器。Google身份验证就是一个示例。

扩展:Siteminder认证

SiteMinder Cookies,其用法,内容和安全性

为Chatkit构建Java身份验证服务器

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

最好您独立部署和管理这四个组件,以实现更好的扩展。例如,在本文中,他们在单一端点中混合了身份验证和令牌生成,这还不是很好-使用Spring Boot的微服务-使用JWT进行身份验证(第3部分)

根据您的撰写,您似乎已经自己编写了第二和第三部分-通常人们为此使用一些现成的工具,例如CA SiteMinder- CA Siteminder的工作原理-基础知识

关于如何生成独特的强令牌的任何技巧?

我建议您通过标准化的方式来获得更好的可维护性和安全性,即选择JWT格式。JSON Web令牌(JWT)身份验证方案

您的令牌将经过签名和加密,因此您还需要一个加密密钥服务器和一种定期旋转这些密钥的机制。

JSON Web令牌-如何安全地存储密钥?

JWT和使用AES手动加密json之间有什么区别?

CA人员已在此社区门户网站上附有详细的pdf指南-可帮助您了解总体流程。

使用REST JWT令牌API的示例代码/应用

您的API代码将需要获取加密密钥,并对令牌进行解密和解码以对令牌进行身份验证。如果令牌被篡改或丢失,则需要对其进行标记。有可用的库。

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

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

JWT应该存储在localStorage还是cookie中?

跨域Cookie

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

如果JWT被盗怎么办?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.