web-dev-qa-db-ja.com

AngularJSによる認証、セッション管理、およびREST Api WSによるセキュリティの問題

AngularJSを使用してWebアプリの開発を開始しましたが、すべてが適切に保護されているかどうかはわかりません(クライアント側とサーバー側)。セキュリティは単一のログインページに基づいており、資格情報が適切にチェックされた場合、サーバーはカスタムの有効期限で一意のトークンを送り返します。他のすべてのREST AP​​Iは、このトークンを介してアクセスできます。アプリケーション(クライアント)は、エントリポイントを参照します。例: 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に保存する方が良いでしょうか?独自の強力なトークンを生成する方法に関するヒントはありますか?

編集(改善):

  • セッショントークンジェネレーターとしてHMAC-SHA256を使用することにしました。有効期限は20分です。ランダムな32バイトのGUIDを生成し、タイムスタンプを添付して、40バイトのキーを提供してHASH-SHA256を計算します。トークンの有効性は非常に小さいため、衝突を取得することはまったく不可能です。
  • Cookieには、セキュリティを強化するための ドメインとパス 属性があります。
  • マルチログインは許可されていません。
66
StarsSky

Httpsを介してサーバーと通信する場合、リプレイ攻撃の問題はありません。

私の提案は、サーバーのセキュリティ技術を活用することです。たとえば、JavaEEには、すぐに使用可能なログインメカニズム、リソース(REST_エンドポイント)の宣言的なロールベースの保護などがあります。これらはすべて、Cookieのセットで管理されますが、ストレージと有効期限に注意する必要があります。サーバー/フレームワークがすでに提供しているものをチェックしてください。

APIをより幅広いユーザー(特に、提供するブラウザーベースのUIに限定されない)または他の種類のクライアント(モバイルアプリなど)に公開する予定がある場合は、OAuthの採用を検討してください。

Angularには、次のセキュリティ機能があります(ポップアップするたびに追加されます)。

CSRF/XSRF攻撃

Angularは、 CSRF 保護のためのすぐに使えるメカニズムをサポートしています。 $httpdocs をご覧ください。サーバー側のサポートが必要です。

コンテンツセキュリティポリシー

Angularには、 CSP が有効な場合に適用されるより厳密なJavaScriptランタイムと互換性のある式評価モードがあります。 ng-cspdocs をご覧ください。

厳密なコンテキストエスケープ

Angularの新しい$sce機能(1.2以降)を使用して、XSS攻撃などに対してUIを強化します。少し便利ではありませんが、より安全です。ドキュメントを確認してください こちら

54
Kos Prov

これは、通常の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]);
                     },
                 }
             })
    });
9
Pramod Sharma
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

第一に、あなたが尋ねたことに対する短い答えはありません。すでに回答済みのものに加えて、さらに何かを追加してみましょう。企業レベルでは、4つの主要なコンポーネントがあります。

  1. UI
  2. ユーザー認証サーバー-ここでユーザー資格情報を検証し、ユーザーがUIで前進するために必要なCookieを生成します。このステップが失敗すると、ユーザーはそこで停止します。このサーバーは、APIトークンの生成とは関係ありません。これは、非APIベースのシステムにも必要です。Google認証はその一例です。

拡張子:Siteminder認証

SiteMinder Cookie、その使用法、コンテンツ、およびセキュリティ

チャットキット用のJava認証サーバーの構築

  1. API Token Server-このサーバーは、手順2で生成されたCookieに基づいてAPIトークンを生成します。つまり、サーバーにCookieを送信してトークンを取得します
  2. APIs-ステップ3で生成されたトークンを使用して、API呼び出しを行います。

スケールを改善するために、これら4つのコンポーネントを個別に展開および管理することをお勧めします。例えばこの記事では、シングルエンドポイントで認証とトークン生成を混同しており、それは良くありません- Spring Bootのマイクロサービス— JWTによる認証(パート3)

書き上げると、コンポーネント2と3を自分で書いたように見えます-通常、人々はCA SiteMinderのような既製のツールを利用します- CA Siteminderの仕組み-基本

独自の強力なトークンを生成する方法に関するヒントはありますか?

保守性とセキュリティを向上させるために、標準化された方法、つまりJWT形式を選択することをお勧めします。 JSON Web Token(JWT)認証スキーム

トークンは署名および暗号化されるため、暗号化キーサーバーと、これらのキーを定期的にローテーションするメカニズムも必要になります。

JSON Web Tokens-キーを安全に保存する方法?

JWTとAESを使用して一部のjsonを手動で暗号化することの違いは何ですか?

CAの担当者は、このコミュニティポータルに詳細なPDFガイドを添付しています。これは、全体的なフローを理解するのに役立ちます。

サンプルコード/ REST JWTトークンAPIを使用するアプリ

APIコードは、暗号化キーを取得し、トークンを復号化およびデコードしてトークンを認証する必要があります。トークンが改ざんまたは欠落している場合は、トークンにフラグを立てる必要があります。このために利用可能なライブラリがあります。

トークンを新しいCookieまたはlocalStorageに保存する方が良いでしょうか?

uIとAPIが異なるドメインにある場合はローカルストレージ、同じドメインにある場合はCookie。

JWTはlocalStorageまたはcookieに保存すべきですか?

クロスドメインCookie

アプリケーションのセキュリティも展開モデルと、質問で指定していない部分に依存します。開発者は、SQLインジェクションのようにコードに単純な欠陥を残す場合があります。

JWTが盗まれた場合はどうなりますか?

1
Sabir Khan