AngularJS : HTTP 인터셉터에 서비스 주입 (순환 종속성)


118

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');

위의 코드는 어디로 가야합니까?

내 질문은 이것을 수행하는 올바른 방법이 무엇입니까?

감사합니다. 제 질문이 충분히 명확했으면합니다.


1
호기심에서 AuthService에서 사용하는 종속성은 무엇입니까? http 인터셉터에서 요청 방법을 사용하여 순환 종속성 문제가 발생하여 여기로 왔습니다. angularfire의 $ firebaseAuth를 사용하고 있습니다. 인젝터에서 $ route를 사용하는 코드 블록 (510 행)을 제거했을 때 모든 것이 작동하기 시작했습니다. 이 문제의 현재는 하지만 인터셉터에서 $ HTTP를 사용하는 방법에 대한합니다. 꺼져!
slamborne 2014 년

흠, 제 경우에는 AuthService가 $ window, $ http, $ location, $ q에 따라 다릅니다
shaunlim

일부 상황에서 인터셉터에서 요청을 재 시도하는 경우가 있으므로 $http. 내가 찾은 유일한 방법은를 사용하는 $injector.get것이지만 이것을 피하기 위해 코드를 구조화하는 좋은 방법이 있는지 아는 것이 좋습니다.
Michal Charemza 2014 년

1
비슷한 문제를 해결 한 @rewritten : github.com/angular/angular.js/issues/2367 의 응답을 살펴보십시오 . 그가하는 일은 다음과 같습니다 : $ http = $ http || $ injector.get ( "$ http"); 물론 $ http를 사용하려는 자신의 서비스로 바꿀 수 있습니다.
Jonathan

답변:


42

$ http와 AuthService간에 순환 종속성이 있습니다.

$injector서비스 를 사용하여 수행하는 작업 은 AuthService에 대한 $ http의 종속성을 지연시켜 닭과 계란 문제를 해결하는 것입니다.

나는 당신이 한 일이 실제로 그것을하는 가장 간단한 방법이라고 믿습니다.

다음과 같이 할 수도 있습니다.

  • 나중에 인터셉터를 등록하면 ( run()블록 대신 블록 에 등록하면 config()이미 트릭을 수행 할 수 있습니다). 하지만 $ http가 이미 호출되지 않았 음을 보장 할 수 있습니까?
  • AuthService.setHttp()또는 무언가 를 호출하여 인터셉터를 등록 할 때 AuthService에 수동으로 $ http를 "주입" 합니다.
  • ...

15
이 답변이 어떻게 문제를 해결하고 있는지 모르겠습니다. @shaunlim
Inanc 구 무스

1
실제로 그것은 그것을 해결하지 않고 알고리즘 흐름이 나쁘다는 것을 지적합니다.
Roman M. Koss 2014

12
run()$ httpProvider를 실행 블록에 삽입 할 수 없기 때문에 블록에 인터셉터를 등록 할 수 없습니다. 구성 단계에서만 수행 할 수 있습니다.
Stephen Friedrich

2
순환 참조에 대한 좋은 점이지만 그렇지 않으면 허용되는 답변이되어서는 안됩니다. 글 머리 기호 중 어느 것도 의미가 없습니다
Nikolai

65

이것이 내가 한 일입니다.

  .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에서 순환 종속성 오류가 계속 발생합니다.


4
수동 주입 ($ injector.get ( 'Auth'))을 사용하면 문제가 해결되었습니다. 잘 했어!
Robert

순환 종속성을 피하기 위해 어떤 URL이 호출되는지 확인하고 있습니다. if (! config.url.includes ( '/ oauth / v2 / token') && config.url.includes ( '/ api')) {// OAuth 서비스 호출}. 따라서 더 이상 순환 종속성이 없습니다. 적어도 나 자신을 위해 그것은 일했습니다;).
Brieuc

완전한. 이것이 바로 비슷한 문제를 해결하는 데 필요한 것입니다. 감사합니다 @shaunlim!
Martyn Chamberlin

이 서비스는 익명이며 테스트 처리가 쉽지 않기 때문에이 솔루션이 마음에 들지 않습니다. 런타임에 주입하는 훨씬 더 나은 솔루션입니다.
kmanzana

그것은 나를 위해 일했습니다. 기본적으로 $ http를 사용하는 서비스를 주입합니다.
토마스

15

$ injector를 직접 사용하는 것은 반 패턴이라고 생각합니다.

순환 종속성을 끊는 방법은 이벤트를 사용하는 것입니다. $ state를 주입하는 대신 $ rootScope를 주입하십시오. 직접 리디렉션하는 대신

this.$rootScope.$emit("unauthorized");

...을 더한

angular
    .module('foo')
    .run(function($rootScope, $state) {
        $rootScope.$on('unauthorized', () => {
            $state.transitionTo('login');
        });
    });

2
나는 이것이 더 우아한 해결책이라고 생각합니다. 의존성이 없을 것입니다. 우리는 또한 관련이있는 곳이면 어디에서
나이

이벤트를 발송 한 후 반환 값을 가질 수 없기 때문에 내 요구에 맞지 않습니다.
xabitrigo

13

잘못된 논리가 그러한 결과를 낳았습니다.

실제로 Http Interceptor에서 사용자가 작성했는지 여부를 찾는 지점이 없습니다. 모든 HTTP 요청을 단일 .service (또는 .factory 또는 .provider)로 래핑하고 모든 요청에 ​​사용하는 것이 좋습니다. 함수를 호출 할 때마다 사용자 로그인 여부를 확인할 수 있습니다. 모두 정상이면 전송 요청을 허용하십시오.

귀하의 경우 Angular 응용 프로그램은 어떤 경우에도 요청을 보내고 거기에서 인증을 확인하면 JavaScript가 요청을 보냅니다.

문제의 핵심

myHttpInterceptor$httpProvider인스턴스에서 호출됩니다 . 귀하의 AuthService사용 $http, 또는 $resource, 그리고 여기 종속성 재귀 또는 순환 종속성이 있습니다. 에서 해당 종속성을 제거 AuthService하면 해당 오류가 표시되지 않습니다.


또한 @Pieter Herroelen이 지적했듯이이 인터셉터를 모듈에 배치 할 수 module.run있지만 이것은 해결책이 아니라 해킹과 비슷할 것입니다.

깔끔하고 자기 설명적인 코드를 작성하려면 몇 가지 SOLID 원칙을 따라야합니다.

최소한 단일 책임 원칙은 이러한 상황에서 많은 도움이 될 것입니다.


5
나는이 대답이 잘 나가셨 생각하지 않습니다,하지만 난 이 문제의 근본에 도달 생각합니다. 현재 사용자 데이터 로그인 수단 (http 요청) 을 저장하는 인증 서비스의 문제 는 가지를 담당한다는 것입니다. 대신 현재 사용자 데이터를 저장하기위한 하나의 서비스와 로그인을위한 다른 서비스로 나누어지면 http 인터셉터는 "현재 사용자 서비스"에만 의존해야하며 더 이상 순환 종속성을 생성하지 않습니다.
Snixtor

@Snixtor 감사합니다! 더 명확하게하려면 영어를 더 많이 배워야합니다.
Roman M. Koss

0

인증 상태 (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 내부에 이것을 설정할 수도 있습니다)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.