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간에 순환 종속성이 있습니다.
What you are doing by using the $injector
service is solving the chicken-and-egg problem by delaying the dependency of $http on the AuthService.
I believe that what you did is actually the simplest way of doing it.
You could also do this by:
- Registering the interceptor later (doing so in a
run()
block instead of aconfig()
block might already do the trick). But can you guarantee that $http hasn't been called already? - "Injecting" $http manually into the AuthService when you're registering the interceptor by calling
AuthService.setHttp()
or something. - ...
This is what I ended up doing
.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);
}
}
};
}]);
}]);
Note: The $injector.get
calls should be within the methods of the interceptor, if you try to use them elsewhere you will continue to get a circular dependency error in JS.
I think using the $injector directly is an antipattern.
A way to break the circular dependency is to use an event: Instead of injecting $state, inject $rootScope. Instead of redirecting directly, do
this.$rootScope.$emit("unauthorized");
plus
angular
.module('foo')
.run(function($rootScope, $state) {
$rootScope.$on('unauthorized', () => {
$state.transitionTo('login');
});
});
Bad logic made such results
Actually there is no point of seeking is user authored or not in Http Interceptor. I would recomend to wrap your all HTTP requests into single .service (or .factory, or into .provider), and use it for ALL requests. On each time you call function, you can check is user logged in or not. If all is ok, allow send request.
In your case, Angular application will send request in any case, you just checking authorization there, and after that JavaScript will send request.
Core of your problem
myHttpInterceptor
is called under $httpProvider
instance. Your AuthService
uses $http
, or $resource
, and here you have dependency recursion, or circular dependency. If your remove that dependency from AuthService
, than you will not see that error.
Also as @Pieter Herroelen pointed, you could place this interceptor in your module module.run
, but this will be more like a hack, not a solution.
If your up to do clean and self descriptive code, you must go with some of SOLID principles.
At least Single Responsibility principle will help you a lot in such situations.
If you're just checking for the Auth state (isAuthorized()) I would recommend to put that state in a separate module, say "Auth", which just holds the state and doesn't use $http itself.
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;
}
}
})
}])
Auth Module:
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
}
(i used localStorage to store the sessionId on client side here, but you can also set this inside your AuthService after a $http call for example)
'IT박스' 카테고리의 다른 글
WPF Numeric UpDown 컨트롤은 어디에 있습니까? (0) | 2020.07.20 |
---|---|
파일 이름에 허용되는 문자 (0) | 2020.07.20 |
Android-스크롤 가능한 제약 조건 레이아웃을 만드는 방법은 무엇입니까? (0) | 2020.07.20 |
동일한 서버의 다른 포트가 도메인 간으로 간주됩니까? (0) | 2020.07.20 |
`is_base_of`는 어떻게 작동합니까? (0) | 2020.07.20 |