[javascript] AngularJS : HTTP 인터셉터에 서비스 주입 (순환 종속성)
AngularJS 앱에서 인증을 처리하기 위해 HTTP 인터셉터를 작성하려고합니다.
이 코드는 작동하지만 Angular가 이것을 자동으로 처리해야한다고 생각했기 때문에 서비스를 수동으로 주입하는 것이 걱정됩니다.
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($location, $injector) {
return {
'request': function (config) {
//injected manually to get around circular dependency problem.
var AuthService = $injector.get('AuthService');
console.log(AuthService);
console.log('in request interceptor');
if (!AuthService.isAuthenticated() && $location.path != '/login') {
console.log('user is not logged in.');
$location.path('/login');
}
return config;
}
};
})
}]);
내가 시작한 일이지만 순환 종속성 문제가 발생했습니다.
app.config(function ($provide, $httpProvider) {
$provide.factory('HttpInterceptor', function ($q, $location, AuthService) {
return {
'request': function (config) {
console.log('in request interceptor.');
if (!AuthService.isAuthenticated() && $location.path != '/login') {
console.log('user is not logged in.');
$location.path('/login');
}
return config;
}
};
});
$httpProvider.interceptors.push('HttpInterceptor');
});
내가 우려하는 또 다른 이유 는 Angular Docs의 $ http 섹션 이 Http 인터셉터에 “일반적인 방법”을 주입하는 방법을 보여주기 때문입니다. “인터셉터”에서 해당 코드 스 니펫을 참조하십시오.
// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
// optional method
'request': function(config) {
// do something on success
return config || $q.when(config);
},
// optional method
'requestError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
},
// optional method
'response': function(response) {
// do something on success
return response || $q.when(response);
},
// optional method
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
};
}
});
$httpProvider.interceptors.push('myHttpInterceptor');
위의 코드는 어디로 가야합니까?
내 질문은 이것을 수행하는 올바른 방법이 무엇입니까?
감사합니다. 제 질문이 충분히 명확했으면합니다.
답변
$ http와 AuthService간에 순환 종속성이 있습니다.
$injector
서비스 를 사용하여 수행하는 작업 은 AuthService에 대한 $ http의 종속성을 지연시켜 닭과 계란 문제를 해결하는 것입니다.
나는 당신이 한 일이 실제로 그것을하는 가장 간단한 방법이라고 믿습니다.
다음과 같이 할 수도 있습니다.
- 나중에 인터셉터를 등록하면 (
run()
블록 대신 블록 에 등록하면config()
이미 트릭을 수행 할 수 있습니다). 하지만 $ http가 이미 호출되지 않았 음을 보장 할 수 있습니까? AuthService.setHttp()
또는 무언가 를 호출하여 인터셉터를 등록 할 때 AuthService에 수동으로 $ http를 “주입” 합니다.- …
답변
이것이 내가 한 일입니다.
.config(['$httpProvider', function ($httpProvider) {
//enable cors
$httpProvider.defaults.useXDomain = true;
$httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) {
return {
'request': function (config) {
//injected manually to get around circular dependency problem.
var AuthService = $injector.get('Auth');
if (!AuthService.isAuthenticated()) {
$location.path('/login');
} else {
//add session_id as a bearer token in header of all outgoing HTTP requests.
var currentUser = AuthService.getCurrentUser();
if (currentUser !== null) {
var sessionId = AuthService.getCurrentUser().sessionId;
if (sessionId) {
config.headers.Authorization = 'Bearer ' + sessionId;
}
}
}
//add headers
return config;
},
'responseError': function (rejection) {
if (rejection.status === 401) {
//injected manually to get around circular dependency problem.
var AuthService = $injector.get('Auth');
//if server returns 401 despite user being authenticated on app side, it means session timed out on server
if (AuthService.isAuthenticated()) {
AuthService.appLogOut();
}
$location.path('/login');
return $q.reject(rejection);
}
}
};
}]);
}]);
참고 : $injector.get
호출은 인터셉터의 메서드 내에 있어야합니다. 다른 곳에서 사용하려고하면 JS에서 순환 종속성 오류가 계속 발생합니다.
답변
$ injector를 직접 사용하는 것은 반 패턴이라고 생각합니다.
순환 종속성을 끊는 방법은 이벤트를 사용하는 것입니다. $ state를 주입하는 대신 $ rootScope를 주입하십시오. 직접 리디렉션하는 대신
this.$rootScope.$emit("unauthorized");
…을 더한
angular
.module('foo')
.run(function($rootScope, $state) {
$rootScope.$on('unauthorized', () => {
$state.transitionTo('login');
});
});
답변
잘못된 논리가 그러한 결과를 낳았습니다.
실제로 Http Interceptor에서 사용자가 작성했는지 여부를 찾는 지점이 없습니다. 모든 HTTP 요청을 단일 .service (또는 .factory 또는 .provider)로 래핑하고 모든 요청에 사용하는 것이 좋습니다. 함수를 호출 할 때마다 사용자 로그인 여부를 확인할 수 있습니다. 모두 정상이면 전송 요청을 허용하십시오.
귀하의 경우 Angular 응용 프로그램은 어떤 경우에도 요청을 보내고 거기에서 인증을 확인하면 JavaScript가 요청을 보냅니다.
문제의 핵심
myHttpInterceptor
$httpProvider
인스턴스에서 호출됩니다 . 귀하의 AuthService
사용 $http
, 또는 $resource
, 그리고 여기 종속성 재귀 또는 순환 종속성이 있습니다. 에서 해당 종속성을 제거 AuthService
하면 해당 오류가 표시되지 않습니다.
또한 @Pieter Herroelen이 지적했듯이이 인터셉터를 모듈에 배치 할 수 module.run
있지만 이것은 해결책이 아니라 해킹과 비슷할 것입니다.
깔끔하고 자기 설명적인 코드를 작성하려면 몇 가지 SOLID 원칙을 따라야합니다.
최소한 단일 책임 원칙은 이러한 상황에서 많은 도움이 될 것입니다.
답변
인증 상태 (isAuthorized ()) 만 확인하는 경우 상태를 유지하고 $ http 자체를 사용하지 않는 “Auth”라고하는 별도의 모듈에 해당 상태를 넣는 것이 좋습니다.
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($location, Auth) {
return {
'request': function (config) {
if (!Auth.isAuthenticated() && $location.path != '/login') {
console.log('user is not logged in.');
$location.path('/login');
}
return config;
}
}
})
}])
인증 모듈 :
angular
.module('app')
.factory('Auth', Auth)
function Auth() {
var $scope = {}
$scope.sessionId = localStorage.getItem('sessionId')
$scope.authorized = $scope.sessionId !== null
//... other auth relevant data
$scope.isAuthorized = function() {
return $scope.authorized
}
return $scope
}
(여기 클라이언트 측에 sessionId를 저장하기 위해 localStorage를 사용했지만 예를 들어 $ http 호출 후 AuthService 내부에 이것을 설정할 수도 있습니다)