使用AngularJS进行身份validation,使用REST API WS进行会话pipe理和安全问题
我开始用angularJS开发一个web应用程序,我不确定所有的东西都是正确的(客户端和服务器端)。 安全性基于单一login页面,如果凭证已选中,我的服务器将发回具有自定义时间有效性的唯一标记。 所有其他REST API都可以通过这个令牌访问。 应用程序(客户端)浏览我的入口点,例如: https : //www.example.com/home.html用户插入凭证并接收一个唯一标记。 这个唯一标记使用AES或其他安全技术存储在服务器数据库中,而不是以清晰的格式存储。
从现在开始,我的AngluarJS应用程序将使用此令牌对所有暴露的REST API进行身份validation。
我正在考虑在一个自定义的http cookie中临时存储令牌; 基本上,当服务器validation凭证时,它会发回一个新的Cookie Ex。
app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T
该cookie具有设置的安全和HTTP Only标志。 Http协议直接pipe理新的cookie并存储它。 连续的请求将呈现cookie的新参数,而不需要pipe理和存储它的JavaScript; 在每一个请求,服务器使令牌失效,并产生一个新的,并将其发送回客户端 – >防止单一令牌的重放攻击。
当客户端收到来自任何REST API的HTTP状态401未经授权的响应时,angular度控制器清除所有cookie并将用户redirect到login页面。
我应该考虑其他方面吗? 将令牌存储在新的cookie或localStorage中更好吗? 任何提示如何生成一个独特的强大的令牌?
编辑(改进):
- 我决定使用HMAC-SHA256作为会话令牌生成器,20分钟有效。 我生成一个随机的32字节的GUID,附加一个时间戳,并通过提供一个40字节的密钥来计算HASH-SHA256。 由于令牌有效性非常小,因此获得冲突是不可能的。
- Cookie将具有域和path属性以增加安全性。
- 不允许多login。
如果您通过https与服务器通话,则不会遇到重播攻击问题。
我的build议是利用你的服务器的安全技术。 例如,JavaEE具有开箱即用的login机制,基于angular色的声明式资源保护(您的REST端点)等等。这些都由一组cookie来pipe理,您不必关心存储和过期。 看看你的服务器/框架已经给你。
如果您打算将API暴露给更广泛的受众(不是专门针对您所服务的基于浏览器的UI)或其他types的客户端(例如移动应用程序),请考虑采用OAuth。
除了我的头顶,Angular还具有以下安全特性(在popup时会添加更多):
CSRF / XSRF攻击
Angular支持一个开箱即用的CSRF保护机制。 看看$http
文档 。 服务器端支持是必要的。
内容安全策略
Angular提供了一种expression式评估模式,与启用CSP时执行的更严格的JavaScript运行时兼容。 查看ng-csp
文档 。
严格的上下文转义
使用Angular的新的$sce
function(1.2+)来加强你的UI对抗XSS攻击等等。它有点不太方便,但更安全。 看看这里的文档。
这是您可以在常规的Angular版本中实现的客户端安全。 我已经试过并testing过了。 (请在这里find我的文章: – http://www.codeproject.com/Tips/811782/AngularJS-Routing-Security )除了客户端路由安全,你还需要在服务器端的安全访问。 客户端安全有助于避免额外往返服务器。 但是,如果有人欺骗浏览器,那么服务器端的安全应该能够拒绝未经授权的访问。
希望这可以帮助!
第1步:在app-module中定义全局variables
定义应用程序的angular色
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(); } } }; });
第三步:在路由中使用安全性:让我们使用我们迄今为止完成的所有的硬件,以确保路由
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]); }, } }) });
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>