我开始使用angularJS开发一个Web应用程序,但我不确定一切都是正确的安全(客户端和服务器端)。 安全性基于单个登录页面,如果凭据检查正常,我的服务器会发回具有自定义时间有效性的唯一令牌。所有其他 REST API 都可以通过此令牌访问。 应用程序(客户端)浏览到我的入口点,例如:https://www.example.com/home.html 用户插入凭据并接收回唯一令牌。此唯一令牌使用AES或其他安全技术存储在服务器数据库中,不会以明文格式存储。
从现在开始,我的AngluarJS应用程序将使用此令牌对所有公开的REST Api进行身份验证。
我正在考虑将令牌临时存储在自定义http cookie中;基本上,当服务器验证凭据时,它会发回一个新的cookie Ex。
app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T
Cookie 设置了安全标志和仅限 HTTP 标志。 Http协议直接管理新的cookie并存储它。连续的请求将为 cookie 提供新参数,而无需对其进行管理并使用 javascript 存储它;在每个请求中,服务器都会使令牌失效并生成一个新令牌并将其发送回客户端 ->防止使用单个令牌进行重放攻击。
当客户端收到来自任何 REST API 的 HTTP 状态401 未授权响应时,角度控制器会清理所有 cookie 并将用户重定向到登录页面。
我应该考虑其他方面吗?将令牌存储在新的cookie中还是存储在localStorage中更好? 关于如何生成唯一强代币的任何提示?
编辑(改进):
- 我决定使用 HMAC-SHA256 作为会话令牌生成器,有效期为 20 分钟。我生成一个随机的 32 字节 GUID,附加一个时间戳,并通过提供 40 字节的密钥来计算 HASH-SHA256。由于令牌有效性非常小,因此不可能获得冲突。
- Cookie 将具有域和路径属性以提高安全性。
- 不允许多次登录。
如果您通过https与服务器通信,则不会遇到重放攻击的问题。
我的建议是利用服务器的安全技术。例如,JavaEE 具有开箱即用的登录机制、基于角色的声明式资源保护(您的 REST 端点)等。这些都是通过一组 cookie 管理的,您不必担心存储和过期。看看你的服务器/框架已经给了你什么。
如果您计划向更广泛的受众(而不是专门针对您提供的基于浏览器的 UI)或其他类型的客户端(例如移动应用程序)公开您的 API,请考虑采用 OAuth。
在我的头顶上,Angular具有以下安全功能(当它们弹出时会添加更多):
CSRF/XSRF 攻击
Angular 支持开箱即用的 CSRF 保护机制。查看$http
文档。需要服务器端支持。
内容安全政策
Angular 具有一种表达式计算模式,该模式与启用 CSP 时强制执行的更严格的 JavaScript 运行时兼容。查看ng-csp
文档。
严格的上下文转义
使用Angular的$sce
新功能(1.2+)来强化UI以抵御XSS攻击等。它不太方便,但更安全。在此处查看文档。
这是客户端安全性,您可以在常规 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]);
},
}
})
});
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>
首先,你所问的问题没有简短或只有一个答案。除了已经回答的内容之外,让我尝试添加更多内容。在企业层面,有四个主要组成部分,
- 用户界面
- 用户身份验证服务器- 在这里,您可以验证用户凭据并生成必要的 Cookie,以便用户在 UI 上继续前进。如果此步骤失败,用户将立即停止。此服务器与API令牌生成无关,对于非基于API的系统,您也需要它。谷歌身份验证就是一个例子。
扩展:站点管理员身份验证
网站管理员 Cookie、其用法、内容和安全性
为 Chatkit 构建 Java 身份验证服务器
- API 令牌服务器
- - 此服务器根据步骤 # 2 中生成的 cookie 生成 API 令牌,即您将 cookie 发送到服务器并获取令牌
- 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中还是存储在 本地存储?
本地存储(如果UI和API位于不同的域上)和Cookie(如果位于同一域上)。
JWT 应该存储在 localStorage 还是 cookie 中?
跨域饼干
应用程序的安全性还取决于部署模型以及您在问题中未指定的部分。有时,开发人员可能会在他们的代码中留下简单的缺陷,如SQL注入:)
如果智威汤逊被盗怎么办?