AngularJSを使用してWebアプリの開発を開始しましたが、すべてが適切に保護されているかどうかはわかりません(クライアント側とサーバー側)。セキュリティは単一のログインページに基づいており、資格情報が適切にチェックされた場合、サーバーはカスタムの有効期限で一意のトークンを送り返します。他のすべてのREST APIは、このトークンを介してアクセスできます。アプリケーション(クライアント)は、エントリポイントを参照します。例: https://www.example.com/home.html ユーザーが資格情報を挿入し、一意のトークンを受け取ります。この一意のトークンは、AESまたはその他の安全な技術を使用してサーバーデータベースに保存されます。明確な形式では保存されません。
これから、私のAngluarJSアプリはこのトークンを使用して、すべてのREST Apiの公開を認証します。
トークンを一時的にカスタムHTTP Cookieに保存することを考えています。基本的に、サーバーが資格情報を確認すると、新しいCookieが返されます。
app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T
Cookieには secure および HTTP Only フラグが設定されています。 Httpプロトコルは、新しいCookieを直接管理して保存します。連続したリクエストは、cookieを管理し、javascriptで保存する必要なく、新しいパラメーターでcookieを提示します。リクエストごとに、サーバーはトークンを無効にし、新しいトークンを生成してクライアントに送り返します->単一のトークンでリプレイ攻撃を防ぎます。
クライアントがREST ApiからHTTPステータス401無許可応答を受信すると、angularコントローラーはすべてのCookieを消去し、ユーザーをリダイレクトしますログインページへ。
他の側面を考慮する必要がありますか?トークンを新しいCookieまたはlocalStorageに保存する方が良いでしょうか?独自の強力なトークンを生成する方法に関するヒントはありますか?
編集(改善):
Httpsを介してサーバーと通信する場合、リプレイ攻撃の問題はありません。
私の提案は、サーバーのセキュリティ技術を活用することです。たとえば、JavaEEには、すぐに使用可能なログインメカニズム、リソース(REST_エンドポイント)の宣言的なロールベースの保護などがあります。これらはすべて、Cookieのセットで管理されますが、ストレージと有効期限に注意する必要があります。サーバー/フレームワークがすでに提供しているものをチェックしてください。
APIをより幅広いユーザー(特に、提供するブラウザーベースのUIに限定されない)または他の種類のクライアント(モバイルアプリなど)に公開する予定がある場合は、OAuthの採用を検討してください。
Angularには、次のセキュリティ機能があります(ポップアップするたびに追加されます)。
CSRF/XSRF攻撃
Angularは、 CSRF 保護のためのすぐに使えるメカニズムをサポートしています。 $http
docs をご覧ください。サーバー側のサポートが必要です。
コンテンツセキュリティポリシー
Angularには、 CSP が有効な場合に適用されるより厳密なJavaScriptランタイムと互換性のある式評価モードがあります。 ng-csp
docs をご覧ください。
厳密なコンテキストエスケープ
Angularの新しい$sce
機能(1.2以降)を使用して、XSS攻撃などに対してUIを強化します。少し便利ではありませんが、より安全です。ドキュメントを確認してください こちら 。
これは、通常の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]);
},
}
})
});
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つの主要なコンポーネントがあります。
SiteMinder Cookie、その使用法、コンテンツ、およびセキュリティ
スケールを改善するために、これら4つのコンポーネントを個別に展開および管理することをお勧めします。例えばこの記事では、シングルエンドポイントで認証とトークン生成を混同しており、それは良くありません- Spring Bootのマイクロサービス— JWTによる認証(パート3)
書き上げると、コンポーネント2と3を自分で書いたように見えます-通常、人々はCA SiteMinderのような既製のツールを利用します- CA Siteminderの仕組み-基本
独自の強力なトークンを生成する方法に関するヒントはありますか?
保守性とセキュリティを向上させるために、標準化された方法、つまりJWT形式を選択することをお勧めします。 JSON Web Token(JWT)認証スキーム
トークンは署名および暗号化されるため、暗号化キーサーバーと、これらのキーを定期的にローテーションするメカニズムも必要になります。
JWTとAESを使用して一部のjsonを手動で暗号化することの違いは何ですか?
CAの担当者は、このコミュニティポータルに詳細なPDFガイドを添付しています。これは、全体的なフローを理解するのに役立ちます。
サンプルコード/ REST JWTトークンAPIを使用するアプリ
APIコードは、暗号化キーを取得し、トークンを復号化およびデコードしてトークンを認証する必要があります。トークンが改ざんまたは欠落している場合は、トークンにフラグを立てる必要があります。このために利用可能なライブラリがあります。
トークンを新しいCookieまたはlocalStorageに保存する方が良いでしょうか?
uIとAPIが異なるドメインにある場合はローカルストレージ、同じドメインにある場合はCookie。
JWTはlocalStorageまたはcookieに保存すべきですか?
アプリケーションのセキュリティも展開モデルと、質問で指定していない部分に依存します。開発者は、SQLインジェクションのようにコードに単純な欠陥を残す場合があります。