68 votes

Authentification avec AngularJS, gestion des sessions et problèmes de sécurité avec REST Api WS

J'ai commencé à développer une application web avec angularJS et je ne suis pas sûr que tout soit bien sécurisé (côté client et serveur). La sécurité est basée sur une seule page de connexion, si les informations d'identification sont vérifiées, mon serveur renvoie un jeton unique avec une validité temporelle personnalisée. Toutes les autres api REST sont accessibles par ce jeton. L'application (client) navigue vers mon point d'entrée ex : https://www.example.com/home.html l'utilisateur insère ses informations d'identification et reçoit en retour un jeton unique. Ce jeton unique est stocké dans la base de données du serveur avec AES ou d'autres techniques sécurisées, il n'est pas stocké en clair.

A partir de maintenant, mon application AngluarJS utilisera ce jeton pour s'authentifier à tous les Api REST exposés.

Je pense à stocker temporairement le jeton dans un cookie http personnalisé ; en gros, lorsque le serveur vérifie les informations d'identification, il renvoie un nouveau cookie Ex.

app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T

Le cookie a le sécurisé y HTTP uniquement drapeaux activés. Le protocole Http gère directement le nouveau cookie et le stocke. Les requêtes successives présenteront le cookie avec le nouveau paramètre, sans avoir besoin de le gérer et de le stocker avec javascript ; à chaque requête, le serveur invalide le jeton et en génère un nouveau qu'il renvoie au client --> empêche les attaques par rejeu avec un seul jeton.

Lorsque le client reçoit un statut HTTP 401 non autorisé à partir de n'importe quel Api REST, le contrôleur angulaire nettoie tous les cookies et redirige l'utilisateur vers la page de connexion.

Dois-je prendre en compte d'autres aspects ? Est-il préférable de stocker le jeton dans un nouveau cookie ou dans localStorage ? Des conseils sur la façon de générer un jeton fort et unique ?

Modifier (améliorations) :

  • J'ai décidé d'utiliser HMAC-SHA256 comme générateur de jeton de session, avec une validité de 20 minutes. Je génère un GUID aléatoire de 32 octets, j'attache un horodatage et je calcule le HASH-SHA256 en fournissant une clé de 40 octets. Il est pratiquement impossible d'obtenir des collisions puisque la validité du jeton est très faible.
  • Cookie aura domaine et chemin attributs pour accroître la sécurité.
  • Les multilogins ne sont pas autorisés.

2 votes

Vous semblez déjà l'être, mais pour que ce soit clair pour tous les autres - utilisez toujours https, sinon le nom d'utilisateur/mot de passe sera envoyé en texte clair.

1 votes

J'ai une question, peut-être simple. Lorsque vous dites que le client reçoit un statut HTTP de 401 du reste, vous nettoyez et redirigez vers la page de connexion. Donc quelque part dans votre code, vous aurez une sorte de condition if pour response.status comme 401. Maintenant, en mode débogage, nous pouvons le changer. Comment gérez-vous cela ? Ou est-il possible qu'un pirate utilise un plugin pour modifier le code d'état de la réponse http ?

1 votes

Vous pouvez tout faire du côté client. Vous pouvez changer le statut 401 http en un statut 200 http et puis ? Vous pouvez faire de l'ingénierie inverse sur le code angulaire et atteindre une page qui fera une demande à un service de repos qui répondra avec un autre 401 :) La chose la plus importante est de sécuriser le côté serveur et de rendre difficile ou impossible à un attaquant d'appeler le rest WS avec une fausse session ou sans session. Je gère donc cela en vérifiant la session sur chaque rest WS et en ne répondant avec la ressource que si la session est valide.

55voto

Kos Prov Points 1742

Si vous communiquez avec le serveur via https, vous n'avez pas de problème avec les attaques par rejeu.

Je vous suggère de tirer parti de la technologie de sécurité de votre serveur. Par exemple, JavaEE dispose d'un mécanisme de connexion prêt à l'emploi, d'une protection déclarative des ressources basée sur les rôles (vos points de terminaison REST), etc. Tout cela est géré par un ensemble de cookies et vous n'avez pas à vous soucier du stockage et de l'expiration. Vérifiez ce que votre serveur/framework vous offre déjà.

Si vous prévoyez d'exposer votre API à un public plus large (pas spécifiquement à l'interface utilisateur basée sur le navigateur que vous servez) ou à d'autres types de clients (par exemple, une application mobile), envisagez d'adopter OAuth.

De mémoire, Angular dispose des fonctions de sécurité suivantes (j'en ajouterai d'autres au fur et à mesure) :

Attaques CSRF/XSRF

Angular prend en charge un mécanisme prêt à l'emploi pour les éléments suivants CSRF protection. Consultez $http docs . Un support côté serveur est nécessaire.

Politique de sécurité du contenu

Angular dispose d'un mode d'évaluation des expressions qui est compatible avec les runtimes JavaScript plus stricts qui sont appliqués quand CSP est activé. Vérifier ng-csp docs .

Échappatoire contextuel strict

Utilisez la nouvelle fonctionnalité d'Angular $sce (1.2+) pour renforcer votre interface utilisateur contre les attaques XSS, etc. C'est un peu moins pratique mais plus sûr. Consultez la documentation aquí .

10voto

Pramod Sharma Points 69

Il s'agit d'une sécurité côté client que vous pouvez mettre en œuvre dans les versions régulières d'Angular. J'ai essayé et testé ceci. (Veuillez trouver mon article ici :-) https://www.intellewings.com/post/authorizationonangularroutes ) En plus de la sécurité des routes côté client, vous devez également sécuriser l'accès côté serveur. La sécurité côté client permet d'éviter les allers-retours supplémentaires vers le serveur. Cependant, si quelqu'un trompe le navigateur, alors la sécurité côté serveur devrait être capable de rejeter l'accès non autorisé.

J'espère que cela vous aidera !

Étape 1 : Définir les variables globales dans le module d'application

-définir les rôles pour l'application

  var roles = {
        superUser: 0,
        admin: 1,
        user: 2
    };

-Définir la route pour l'accès non autorisé pour l'application.

 var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';

Étape 2 : Définir le service pour l'autorisation

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();
        }
    }

};
});

Étape 3 : Utiliser la sécurité dans le routage : Utilisons tout ce que nous avons fait jusqu'à présent pour sécuriser les routes.

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

0 votes

Merci de partager cet extrait. Je vais l'essayer.

2voto

Jaydeep Gondaliya Points 522
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>

2voto

Sabir Khan Points 4656

Premièrement, il n'y a pas de réponse courte ou unique à votre question. En plus de ce qui a déjà été répondu, laissez-moi essayer d'ajouter quelque chose de plus. Au niveau de l'entreprise, il y a quatre composants principaux,

  1. UI
  2. Serveur d'authentification des utilisateurs - Ici, vous validez les informations d'identification de l'utilisateur et générez les cookies nécessaires pour que l'utilisateur puisse avancer dans l'interface utilisateur. Si cette étape échoue, l'utilisateur est stoppé net. Ce serveur n'a rien à voir avec la génération de jetons d'API et vous en avez également besoin pour les systèmes non basés sur des API, comme par exemple Google Authentication.

Extension:Authentification Siteminder

Les cookies SiteMinder, leur utilisation, leur contenu et leur sécurité

Construction d'un serveur d'authentification Java pour Chatkit

  1. Serveur de jetons d'API - Ce serveur génère des jetons API sur la base des cookies générés à l'étape 2, c'est-à-dire que vous envoyez des cookies au serveur et obtenez un jeton.
  2. APIs - Vous utilisez le jeton généré à l'étape # 3 pour faire des appels API.

Il est préférable de déployer et de gérer ces quatre composants indépendamment pour une meilleure échelle. Par exemple, dans cet article, ils ont mélangé l'authentification et la génération de jetons dans un seul point final, ce qui n'est pas bon. Microservices avec Spring Boot - Authentification avec JWT (partie 3)

D'après votre texte, il semble que vous ayez écrit les composants deux et trois par vous-même. En général, les gens utilisent des outils prêts à l'emploi pour cela, comme CA SiteMinder. Comment fonctionne CA Siteminder - Principes de base

Des conseils sur la façon de générer un jeton fort unique ?

Je vous suggère de suivre une voie normalisée pour une meilleure maintenabilité et sécurité, c'est-à-dire de choisir le format JWT. Schéma d'authentification par jeton Web JSON (JWT)

Votre jeton sera signé et crypté. Vous aurez donc besoin d'un serveur de clés de cryptage et d'un mécanisme pour faire tourner ces clés à intervalles réguliers.

JSON Web Tokens - Comment stocker la clé en toute sécurité ?

Quelle est la différence entre JWT et le cryptage manuel d'un fichier json avec AES ?

CA person a joint un guide détaillé en pdf sur ce portail communautaire - qui vous aidera à comprendre le déroulement général.

Exemple de code / application pour utiliser l'API REST JWT token

Votre code API devra récupérer la clé de cryptage et décrypter et décoder le jeton pour authentifier le jeton. Si le jeton est altéré ou manquant, vous devez le signaler comme tel. Des bibliothèques sont disponibles pour cela.

Est-il préférable de stocker le jeton dans un nouveau cookie ou dans le fichier localStorage ?

Stockage local si l'interface utilisateur et l'API sont sur des domaines différents et Cookies si sur le même domaine.

JWT doit-il être stocké dans localStorage ou dans un cookie ?

Cookies inter-domaines

La sécurité d'une application dépend également du modèle de déploiement et cette partie n'a pas été précisée dans votre question. Parfois, les développeurs peuvent laisser des failles aussi simples dans leur code que l'injection SQL :)

Que se passe-t-il si JWT est volé ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X