AngularJS: Đưa dịch vụ vào bộ đánh chặn HTTP (Phụ thuộc vòng)


118

Tôi đang cố gắng viết một trình đánh chặn HTTP cho ứng dụng AngularJS của mình để xử lý xác thực.

Mã này hoạt động, nhưng tôi lo ngại về việc tiêm dịch vụ theo cách thủ công vì tôi nghĩ Angular phải tự động xử lý điều này:

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

Những gì tôi bắt đầu làm, nhưng gặp phải các vấn đề phụ thuộc vòng tròn:

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

Một lý do khác khiến tôi lo ngại là phần trên $ http trong Angular Docs dường như chỉ ra một cách để đưa các phần phụ thuộc vào "một cách thông thường" vào bộ đánh chặn Http. Xem đoạn mã của họ trong "Bộ chặn":

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

Đoạn mã trên nên đi đâu?

Tôi đoán câu hỏi của tôi là cách thích hợp để làm điều này là gì?

Cảm ơn, và tôi hy vọng câu hỏi của tôi đủ rõ ràng.


1
Chỉ vì tò mò, những phụ thuộc nào - nếu có - nơi bạn sử dụng trong AuthService của mình? Tôi đang gặp vấn đề phụ thuộc vòng tròn khi sử dụng phương thức yêu cầu trong trình chặn http, điều này đã đưa tôi đến đây. Tôi đang sử dụng $ firebaseAuth của anglefire. Khi tôi xóa khối mã sử dụng $ route khỏi bộ phun (dòng 510), mọi thứ bắt đầu hoạt động. Có một vấn đề ở đây nhưng đó là về việc sử dụng $ http trong bộ chặn. Chuyển sang git!
slamborne

Hm, cho những gì nó có giá trị, AuthService trong trường hợp của tôi phụ thuộc vào $ cửa sổ, $ http, $ vị trí, $ q
shaunlim

Tôi có một trường hợp thử lại yêu cầu trong bộ đánh chặn trong một số trường hợp, do đó, có sự phụ thuộc vòng tròn thậm chí ngắn hơn vào $http. Cách duy nhất mà tôi tìm thấy là sử dụng $injector.get, nhưng sẽ thật tuyệt nếu biết liệu có cách tốt nào để cấu trúc mã để tránh điều này.
Michal Charemza

1
Hãy xem phản hồi từ @rew write: github.com/angular/angular.js/issues/2367 đã khắc phục sự cố tương tự cho tôi. Những gì anh ấy đang làm là như thế này: $ http = $ http || $ injectionor.get ("$ http"); tất nhiên bạn có thể thay thế $ http bằng dịch vụ của riêng bạn mà bạn đang cố gắng sử dụng.
Jonathan

Câu trả lời:


42

Bạn có sự phụ thuộc vòng giữa $ http và AuthService của mình.

Những gì bạn đang làm bằng cách sử dụng $injectordịch vụ là giải quyết vấn đề con gà và quả trứng bằng cách trì hoãn sự phụ thuộc của $ http vào AuthService.

Tôi tin rằng những gì bạn đã làm thực sự là cách làm đơn giản nhất.

Bạn cũng có thể làm điều này bằng cách:

  • Đăng ký bộ chặn sau (làm như vậy trong một run()khối thay vì một config()khối có thể đã thực hiện được thủ thuật). Nhưng bạn có thể đảm bảo rằng $ http chưa được gọi không?
  • "Tiêm" $ http theo cách thủ công vào AuthService khi bạn đang đăng ký bộ chặn bằng cách gọi điện AuthService.setHttp()hoặc một cái gì đó.
  • ...

15
Tôi không thấy câu trả lời này giải quyết vấn đề như thế nào? @shaunlim
Inanc Gumus

1
Trên thực tế, nó không giải quyết được nó, nó chỉ chỉ ra rằng luồng thuật toán rất tệ.
Roman M. Koss,

12
Bạn không thể đăng ký bộ chặn trong một run()khối, vì bạn không thể đưa $ httpProvider vào một khối đang chạy. Bạn chỉ có thể làm điều đó trong giai đoạn cấu hình.
Stephen Friedrich

2
Điểm tốt là tham chiếu vòng tròn, nhưng nếu không thì nó không phải là một câu trả lời được chấp nhận. Không có gạch đầu dòng nào có ý nghĩa
Nikolai

65

Đây là những gì tôi đã làm

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

Lưu ý: Các $injector.getlệnh gọi phải nằm trong các phương thức của bộ chặn, nếu bạn cố gắng sử dụng chúng ở nơi khác, bạn sẽ tiếp tục gặp lỗi phụ thuộc vòng tròn trong JS.


4
Sử dụng thủ công tiêm ($ injectionor.get ('Auth')) đã giải quyết được vấn đề. Làm tốt lắm!
Robert

Để tránh phụ thuộc vòng tròn, tôi đang kiểm tra url nào được gọi. if (! config.url.includes ('/ oauth / v2 / token') && config.url.includes ('/ api')) {// Gọi Dịch vụ OAuth}. Do đó không có sự phụ thuộc vòng tròn nữa. Ít nhất đối với bản thân tôi nó đã hoạt động;).
Brieuc

Hoàn hảo. Đây chính xác là những gì tôi cần để khắc phục sự cố tương tự. Cảm ơn @shaunlim!
Martyn Chamberlin

Tôi không thực sự thích giải pháp này vì sau đó dịch vụ này là ẩn danh và không dễ xử lý cho các thử nghiệm. Giải pháp tốt hơn nhiều để tiêm trong thời gian chạy.
kmanzana

Điều đó đã làm việc cho tôi. Về cơ bản tiêm dịch vụ đang sử dụng $ http.
Thomas

15

Tôi nghĩ rằng sử dụng trực tiếp bộ phun $ là một phản vật chất.

Một cách để phá vỡ sự phụ thuộc vòng tròn là sử dụng một sự kiện: Thay vì tiêm $ state, hãy tiêm $ rootScope. Thay vì chuyển hướng trực tiếp, hãy làm

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

thêm

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

2
Tôi nghĩ rằng đây là giải pháp thanh lịch hơn, vì nó sẽ không có bất kỳ sự phụ thuộc, chúng ta cũng có thể nghe sự kiện này tại nhiều nơi bất cứ nơi nào có liên quan
Basav

Điều này sẽ không hoạt động cho nhu cầu của tôi, vì tôi không thể có giá trị trả lại sau khi gửi một sự kiện.
xabitrigo

13

Logic tồi đã tạo ra kết quả như vậy

Trên thực tế, không có điểm tìm kiếm là người dùng có tác giả hay không trong Http Interceptor. Tôi khuyên bạn nên gói tất cả các yêu cầu HTTP của bạn thành một .service (hoặc .factory hoặc thành .provider) và sử dụng nó cho TẤT CẢ các yêu cầu. Trên mỗi lần gọi chức năng, bạn có thể kiểm tra xem người dùng đã đăng nhập hay chưa. Nếu tất cả đều ổn, hãy cho phép gửi yêu cầu.

Trong trường hợp của bạn, ứng dụng Angular sẽ gửi yêu cầu trong mọi trường hợp, bạn chỉ cần kiểm tra ủy quyền ở đó và sau đó JavaScript sẽ gửi yêu cầu.

Cốt lõi của vấn đề của bạn

myHttpInterceptorđược gọi dưới $httpProviderthể hiện. Việc AuthServicesử dụng của bạn $http, hoặc $resource, và ở đây bạn có đệ quy phụ thuộc hoặc phụ thuộc vòng tròn. Nếu bạn loại bỏ phụ thuộc đó khỏi AuthService, bạn sẽ không thấy lỗi đó.


Cũng như @Pieter Herroelen đã chỉ ra, bạn có thể đặt thiết bị đánh chặn này trong mô-đun của mình module.run , nhưng điều này sẽ giống như một cuộc tấn công hơn, không phải là một giải pháp.

Nếu bạn muốn làm mã mô tả rõ ràng và tự mô tả, bạn phải tuân theo một số nguyên tắc SOLID.

Ít nhất nguyên tắc Trách nhiệm Đơn lẻ sẽ giúp bạn rất nhiều trong những tình huống như vậy.


5
Tôi không nghĩ rằng câu trả lời này cũng được diễn đạt, nhưng tôi làm nghĩ nó được vào thư mục gốc của vấn đề. Vấn đề với dịch vụ auth lưu trữ dữ liệu người dùng hiện tại phương tiện đăng nhập (yêu cầu http) là nó phải chịu trách nhiệm về hai điều. Nếu thay vào đó, điều đó được chia thành một dịch vụ để lưu trữ dữ liệu người dùng hiện tại và một dịch vụ khác để đăng nhập, thì bộ chặn http chỉ cần phụ thuộc vào "dịch vụ người dùng hiện tại" và không còn tạo ra sự phụ thuộc vòng tròn nữa.
Snixtor

@Snixtor Cảm ơn bạn! Tôi cần học tiếng Anh nhiều hơn, để rõ ràng hơn.
Roman M. Koss

0

Nếu bạn chỉ đang kiểm tra trạng thái Auth (isAuthorized ()), tôi khuyên bạn nên đặt trạng thái đó trong một mô-đun riêng biệt, chẳng hạn như "Auth", chỉ giữ trạng thái và không sử dụng $ http.

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

Mô-đun xác thực:

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
}

(Tôi đã sử dụng localStorage để lưu trữ sessionId ở phía máy khách tại đây, nhưng bạn cũng có thể đặt điều này bên trong AuthService của mình sau một cuộc gọi $ http chẳng hạn)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.