AngularJS를 사용한 인증, REST Api WS를 사용한 세션 관리 및 보안 문제
angularJS로 웹 앱을 개발하기 시작했는데 모든 것이 안전하다고 확신 할 수 없습니다 (클라이언트 및 서버 측). 보안은 단일 로그인 페이지를 기반으로합니다. 자격 증명이 확인되면 내 서버가 사용자 지정 시간 유효성이있는 고유 토큰을 다시 보냅니다. 다른 모든 REST API는이 토큰을 통해 액세스 할 수 있습니다. 응용 프로그램 (클라이언트)은 내 진입 점 (예 : https://www.example.com/home.html 사용자 삽입 자격 증명 )을 찾아 고유 한 토큰을 다시받습니다. 이 고유 토큰은 AES 또는 기타 보안 기술을 사용하여 서버 데이터베이스에 저장되며 일반 형식으로 저장되지 않습니다.
이제부터 내 AngluarJS 앱은이 토큰을 사용하여 노출 된 모든 REST API를 인증합니다.
사용자 지정 http 쿠키에 토큰을 임시 저장하려고합니다. 기본적으로 서버가 자격 증명을 확인할 때 새 쿠키 Ex를 다시 보냅니다.
app-token : AIXOLQRYIlWTXOLQRYI3XOLQXOLQRYIRYIFD0T
쿠키에는 보안 및 HTTP 전용 플래그가 설정되어 있습니다. HTTP 프로토콜은 새 쿠키를 직접 관리하고 저장합니다. 연속적인 요청은 쿠키를 관리하고 자바 스크립트로 저장할 필요없이 새 매개 변수와 함께 쿠키를 제공합니다. 요청이있을 때마다 서버는 토큰을 무효화하고 새 토큰을 생성 한 후 클라이언트로 다시 보냅니다 .--> 단일 토큰으로 재생 공격을 방지합니다.
클라이언트가 REST API로부터 HTTP 상태 401 무단 응답을 받으면 각도 컨트롤러는 모든 쿠키를 정리하고 사용자를 로그인 페이지로 리디렉션합니다.
다른 측면을 고려해야합니까? 새 쿠키 또는 localStorage에 토큰을 저장하는 것이 더 낫습니까? 고유 한 강력한 토큰을 생성하는 방법에 대한 팁이 있습니까?
편집 (개선) :
- 저는 HMAC-SHA256을 세션 토큰 생성기로 20 분 동안 사용하기로 결정했습니다. 임의의 32 바이트 GUID를 생성하고 타임 스탬프를 첨부하고 40 바이트 키를 제공하여 HASH-SHA256을 계산합니다. 토큰 유효성이 아주 적기 때문에 충돌을 얻는 것은 매우 불가능합니다.
- 쿠키에는 보안을 강화하기 위해 도메인 및 경로 속성이 있습니다.
- 다중 로그인은 허용되지 않습니다.
https를 통해 서버와 대화하면 리플레이 공격에 문제가 없습니다.
내 제안은 서버의 보안 기술을 활용하는 것입니다. 예를 들어 JavaEE에는 즉시 사용 가능한 로그인 메커니즘, 선언적 역할 기반 리소스 보호 (REST 엔드 포인트) 등이 있습니다. 이들은 모두 쿠키 세트로 관리되며 스토리지 및 스토리지에 대해 신경 쓸 필요가 없습니다. 만료. 서버 / 프레임 워크가 이미 제공하는 것을 확인하십시오.
API를 더 광범위한 대상 (제공하는 브라우저 기반 UI가 아닌) 또는 다른 유형의 클라이언트 (예 : 모바일 앱)에 노출하려는 경우 OAuth를 채택하는 것이 좋습니다.
내 머리 꼭대기에서 Angular에는 다음과 같은 보안 기능이 있습니다 (튀어 나오면 더 추가됩니다).
CSRF / XSRF 공격
Angular는 CSRF 보호를 위해 즉시 사용 가능한 메커니즘을 지원합니다 . $http
문서를 확인하십시오 . 서버 측 지원이 필요합니다.
콘텐츠 보안 정책
Angular에는 CSP 가 활성화 될 때 적용되는보다 엄격한 JavaScript 런타임과 호환되는 식 평가 모드 가 있습니다. ng-csp
문서를 확인하십시오 .
엄격한 컨텍스트 이스케이프
Angular의 새로운 $sce
기능 (1.2+)을 사용하여 XSS 공격 등에 대한 UI를 강화하십시오. 다소 덜 편리하지만 더 안전합니다. 여기 에서 문서를 확인 하십시오 .
이것은 일반 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>
First, there is no short or only one answer to what you have asked. In addition to what has already been answered, let me try to add something more. At enterprise level , there are four major components ,
- UI
- User Authentication Server - Here you validate user credentials and generate necessary cookies for user to move forward on UI. If this step fails, user gets stopped right there. This server has nothing to do with API token generation & you need this for non - API based systems too.Google Authentication is one example.
Extension:Siteminder Authentication
SiteMinder Cookies, their Usage, Contents and Security
Building a Java authentication server for Chatkit
- API Token Server - This server generates API tokens based on cookies generated on step # 2 i.e. you send cookies to server and get a token
- APIs - You use token generated in step # 3 to make API calls.
Its better that you deploy & manage these four components independently for better scale . e.g. in this article, they have mixed up authentication & token generation in single end point & thats not good - Microservices with Spring Boot — Authentication with JWT (Part 3)
By your write up, it looks that you have written component two & three on your own - usually folks utilize some ready made tools for this like CA SiteMinder - How CA Siteminder works – Basics
Any tips on how to generate a unique strong token?
I would suggest that you go via standardized way for better maintainability & security i.e. you choose JWT format. JSON Web Token (JWT) Authentication Scheme
Your token will be signed & encrypted so You would also need an encryption key server & a mechanism to rotate these keys at regular intervals.
JSON Web Tokens - How to securely store the key?
What is the difference between JWT and encrypting some json manually with AES?
CA person has attached a detailed pdf guide on this community portal - that will help you to understand the overall flow.
Sample Code / App to use of REST JWT token API
Your API code will need to fetch the encryption key and decrypt & decode the token to authenticate token. If token is tampered or missing, you need to flag it as such. There are libraries available for this.
Is it better to store the token inside a new cookie or in localStorage?
local Storage if UI & API are on different domains & Cookies if on same domain.
Should JWT be stored in localStorage or cookie?
Security of an application is also dependent on deployment model and that part you haven't specified in your question. Sometimes , developers might leave as simple flaws in their code as SQL Injection :)
'Nice programing' 카테고리의 다른 글
MATCH AGAINST 또는 LIKE 중 어떤 SQL 쿼리가 더 낫습니까? (0) | 2020.11.15 |
---|---|
JSON 구조 문서화를위한 구문 (0) | 2020.11.15 |
ASP.NET MVC 컨트롤러의 수명은 얼마입니까? (0) | 2020.11.15 |
시나트라 대 레일스 (0) | 2020.11.15 |
iframe src가로드에 실패하면 오류를 포착합니다. (0) | 2020.11.15 |