การรับรองความถูกต้องเข้าสู่ระบบ AngularJS ui-router


376

ฉันใหม่กับ AngularJS และฉันสับสนเล็กน้อยเกี่ยวกับวิธีใช้ angular- "ui-router" ในสถานการณ์ต่อไปนี้:

ฉันกำลังสร้างโปรแกรมประยุกต์บนเว็บซึ่งประกอบด้วยสองส่วน ส่วนแรกคือหน้าแรกที่มีมุมมองเข้าสู่ระบบและการลงชื่อสมัครใช้และส่วนที่สองคือแผงควบคุม (หลังจากลงชื่อเข้าใช้สำเร็จ)

ฉันสร้างส่วนindex.htmlสำหรับโฮมด้วยแอพเชิงมุมและตั้งค่าui-routerเพื่อจัดการ/loginและ/signupดูและมีไฟล์อีกส่วนdashboard.htmlสำหรับแผงควบคุมพร้อมแอพและกำหนดค่าui-routerเพื่อจัดการมุมมองย่อยมากมาย

ตอนนี้ฉันเสร็จส่วนแดชบอร์ดและไม่ทราบวิธีรวมสองส่วนเข้ากับแอพเชิงมุมที่ต่างกัน ฉันจะบอกแอพโฮมให้เปลี่ยนเส้นทางไปยังแอปแดชบอร์ดได้อย่างไร


1
คุณสามารถแบ่งปันรหัสกับเราได้ไหม
Chancho

6
@Chancho ฉันคิดว่ามันไม่เกี่ยวกับรหัสจริงๆฉันไม่รู้รหัสที่ฉันควรแบ่งปัน
Ahmed Hashem

ใช่โปรดแบ่งปันรหัสคำถามทั่วไปมาก ...
Alireza

คำตอบ:


607

ฉันกำลังอยู่ระหว่างการทำตัวอย่างที่ดีกว่ารวมถึงการทำความสะอาดบริการเหล่านี้ให้เป็นโมดูลที่ใช้งานได้ แต่นี่คือสิ่งที่ฉันได้ทำมา นี่เป็นกระบวนการที่ซับซ้อนในการหลีกเลี่ยงข้อควรระวังบางประการ คุณจะต้องแบ่งมันออกเป็นหลายชิ้น

ลองดูที่เสียงพึมพำนี้

ก่อนอื่นคุณต้องมีบริการเพื่อจัดเก็บข้อมูลประจำตัวของผู้ใช้ principalผมเรียกนี้ สามารถตรวจสอบเพื่อดูว่าผู้ใช้เข้าสู่ระบบและตามคำขอมันสามารถแก้ไขวัตถุที่แสดงถึงข้อมูลที่จำเป็นเกี่ยวกับตัวตนของผู้ใช้ สิ่งนี้อาจเป็นสิ่งที่คุณต้องการ แต่สิ่งสำคัญคือชื่อที่แสดงชื่อผู้ใช้อีเมลและบทบาทที่ผู้ใช้เป็นเจ้าของ (หากมีผลกับแอปของคุณ) อาจารย์ใหญ่ยังมีวิธีการตรวจสอบบทบาท

.factory('principal', ['$q', '$http', '$timeout',
  function($q, $http, $timeout) {
    var _identity = undefined,
      _authenticated = false;

    return {
      isIdentityResolved: function() {
        return angular.isDefined(_identity);
      },
      isAuthenticated: function() {
        return _authenticated;
      },
      isInRole: function(role) {
        if (!_authenticated || !_identity.roles) return false;

        return _identity.roles.indexOf(role) != -1;
      },
      isInAnyRole: function(roles) {
        if (!_authenticated || !_identity.roles) return false;

        for (var i = 0; i < roles.length; i++) {
          if (this.isInRole(roles[i])) return true;
        }

        return false;
      },
      authenticate: function(identity) {
        _identity = identity;
        _authenticated = identity != null;
      },
      identity: function(force) {
        var deferred = $q.defer();

        if (force === true) _identity = undefined;

        // check and see if we have retrieved the 
        // identity data from the server. if we have, 
        // reuse it by immediately resolving
        if (angular.isDefined(_identity)) {
          deferred.resolve(_identity);

          return deferred.promise;
        }

        // otherwise, retrieve the identity data from the
        // server, update the identity object, and then 
        // resolve.
        //           $http.get('/svc/account/identity', 
        //                     { ignoreErrors: true })
        //                .success(function(data) {
        //                    _identity = data;
        //                    _authenticated = true;
        //                    deferred.resolve(_identity);
        //                })
        //                .error(function () {
        //                    _identity = null;
        //                    _authenticated = false;
        //                    deferred.resolve(_identity);
        //                });

        // for the sake of the demo, fake the lookup
        // by using a timeout to create a valid
        // fake identity. in reality,  you'll want 
        // something more like the $http request
        // commented out above. in this example, we fake 
        // looking up to find the user is
        // not logged in
        var self = this;
        $timeout(function() {
          self.authenticate(null);
          deferred.resolve(_identity);
        }, 1000);

        return deferred.promise;
      }
    };
  }
])

ประการที่สองคุณต้องการบริการที่ตรวจสอบสถานะที่ผู้ใช้ต้องการตรวจสอบให้แน่ใจว่าพวกเขากำลังเข้าสู่ระบบ (ถ้าจำเป็นไม่จำเป็นสำหรับการลงชื่อเข้าใช้รีเซ็ตรหัสผ่าน ฯลฯ ) จากนั้นทำการตรวจสอบบทบาท (ถ้าแอปของคุณ ต้องการสิ่งนี้) หากไม่ได้รับการรับรองความถูกต้องให้ส่งพวกเขาไปที่หน้าลงชื่อเข้าใช้ หากได้รับการรับรองความถูกต้อง แต่ไม่ผ่านการตรวจสอบบทบาทให้ส่งพวกเขาไปยังหน้าปฏิเสธการเข้าถึง ฉันเรียกบริการauthorizationนี้

.factory('authorization', ['$rootScope', '$state', 'principal',
  function($rootScope, $state, principal) {
    return {
      authorize: function() {
        return principal.identity()
          .then(function() {
            var isAuthenticated = principal.isAuthenticated();

            if ($rootScope.toState.data.roles
                && $rootScope.toState
                             .data.roles.length > 0 
                && !principal.isInAnyRole(
                   $rootScope.toState.data.roles))
            {
              if (isAuthenticated) {
                  // user is signed in but not
                  // authorized for desired state
                  $state.go('accessdenied');
              } else {
                // user is not authenticated. Stow
                // the state they wanted before you
                // send them to the sign-in state, so
                // you can return them when you're done
                $rootScope.returnToState
                    = $rootScope.toState;
                $rootScope.returnToStateParams
                    = $rootScope.toStateParams;

                // now, send them to the signin state
                // so they can log in
                $state.go('signin');
              }
            }
          });
      }
    };
  }
])

ตอนนี้สิ่งที่คุณต้องทำคือการฟังใน'sui-router $stateChangeStartสิ่งนี้ให้โอกาสคุณในการตรวจสอบสถานะปัจจุบันสถานะที่ต้องการไปและแทรกการตรวจสอบสิทธิ์ของคุณ หากล้มเหลวคุณสามารถยกเลิกการเปลี่ยนเส้นทางหรือเปลี่ยนเป็นเส้นทางอื่น

.run(['$rootScope', '$state', '$stateParams', 
      'authorization', 'principal',
    function($rootScope, $state, $stateParams, 
             authorization, principal)
{
      $rootScope.$on('$stateChangeStart', 
          function(event, toState, toStateParams)
      {
        // track the state the user wants to go to; 
        // authorization service needs this
        $rootScope.toState = toState;
        $rootScope.toStateParams = toStateParams;
        // if the principal is resolved, do an 
        // authorization check immediately. otherwise,
        // it'll be done when the state it resolved.
        if (principal.isIdentityResolved()) 
            authorization.authorize();
      });
    }
  ]);

ส่วนที่ยุ่งยากเกี่ยวกับการติดตามข้อมูลประจำตัวของผู้ใช้คือการค้นหาถ้าคุณได้ทำการรับรองความถูกต้องแล้ว (เช่นคุณกำลังเยี่ยมชมหน้าเว็บหลังจากเซสชันก่อนหน้านี้และบันทึกโทเค็นการตรวจสอบสิทธิ์ในคุกกี้หรืออาจรีเฟรชหน้าเว็บยากหรือ วางลงบน URL จากลิงค์) เนื่องจากวิธีการui-routerทำงานคุณต้องทำการแก้ไขตัวตนของคุณหนึ่งครั้งก่อนที่จะตรวจสอบการรับรองความถูกต้องของคุณ คุณสามารถทำได้โดยใช้resolveตัวเลือกในการกำหนดค่าสถานะของคุณ ฉันมีสถานะผู้ปกครองเดียวสำหรับไซต์ที่ทุกรัฐสืบทอดมาซึ่งบังคับให้เงินต้นได้รับการแก้ไขก่อนสิ่งอื่นใดจะเกิดขึ้น

$stateProvider.state('site', {
  'abstract': true,
  resolve: {
    authorize: ['authorization',
      function(authorization) {
        return authorization.authorize();
      }
    ]
  },
  template: '<div ui-view />'
})

มีปัญหาอื่นที่นี่ ... resolveได้รับเรียกเพียงครั้งเดียว เมื่อสัญญาของคุณสำหรับการค้นหาตัวตนเสร็จสมบูรณ์แล้วจะไม่เรียกใช้ตัวแทนการแก้ไขปัญหาอีกครั้ง ดังนั้นเราต้องทำการตรวจสอบความถูกต้องของคุณในสองสถานที่: ครั้งหนึ่งเคยเป็นไปตามตัวตนของคุณสัญญาแก้ไขresolveซึ่งครอบคลุมครั้งแรกที่แอพของคุณโหลดและอีกครั้งใน$stateChangeStartกรณีที่มีการแก้ปัญหาซึ่งครอบคลุมทุกครั้งที่คุณนำทางทั่วรัฐ

ตกลงเราทำอะไรไปแล้ว

  1. เราตรวจสอบว่าแอปโหลดเมื่อใดหากผู้ใช้ลงชื่อเข้าใช้
  2. เราติดตามข้อมูลเกี่ยวกับผู้ใช้ที่เข้าสู่ระบบ
  3. เราเปลี่ยนเส้นทางพวกเขาไปสู่สถานะลงชื่อเข้าใช้สำหรับรัฐที่กำหนดให้ผู้ใช้ต้องเข้าสู่ระบบ
  4. เราเปลี่ยนเส้นทางพวกเขาไปสู่สถานะถูกปฏิเสธหากพวกเขาไม่ได้รับอนุญาตให้เข้าถึง
  5. เรามีกลไกในการเปลี่ยนเส้นทางผู้ใช้กลับสู่สถานะเดิมที่พวกเขาร้องขอหากเราต้องการให้พวกเขาเข้าสู่ระบบ
  6. เราสามารถออกจากระบบผู้ใช้ (ต้องต่อสายเข้าด้วยกันกับลูกค้าหรือรหัสเซิร์ฟเวอร์ที่จัดการตั๋วรับรองความถูกต้องของคุณ)
  7. เราไม่จำเป็นต้องส่งผู้ใช้กลับไปที่หน้าลงชื่อเข้าใช้ทุกครั้งที่พวกเขาโหลดเบราว์เซอร์ใหม่หรือวางลิงก์

เราไปจากที่นี่ที่ไหน ดีที่คุณสามารถจัดระเบียบรัฐของคุณลงในพื้นที่ที่ต้องมีการลงชื่อเข้าใช้. คุณจะต้องรับรองความถูกต้อง / ผู้ใช้อำนาจโดยการเพิ่มdataกับrolesเหล่านี้รัฐ (หรือผู้ปกครองของพวกเขาถ้าคุณต้องการที่จะใช้มรดก) ที่นี่เรา จำกัด ทรัพยากรสำหรับผู้ดูแลระบบ:

.state('restricted', {
    parent: 'site',
    url: '/restricted',
    data: {
      roles: ['Admin']
    },
    views: {
      'content@': {
        templateUrl: 'restricted.html'
      }
    }
  })

ตอนนี้คุณสามารถควบคุมสถานะของสิ่งที่ผู้ใช้สามารถเข้าถึงเส้นทาง ปัญหาอื่น ๆ ? อาจจะแตกต่างกันเพียงบางส่วนของมุมมองขึ้นอยู่กับว่าพวกเขาเข้าสู่ระบบหรือไม่? ไม่มีปัญหา. ใช้principal.isAuthenticated()หรือแม้กระทั่งprincipal.isInRole()ด้วยวิธีต่าง ๆ มากมายที่คุณสามารถแสดงแม่แบบหรือองค์ประกอบ

ก่อนอื่นให้ฉีดprincipalเข้าไปในคอนโทรลเลอร์หรืออะไรก็ตามและติดกับขอบเขตเพื่อให้คุณสามารถใช้งานได้ง่ายในมุมมองของคุณ:

.scope('HomeCtrl', ['$scope', 'principal', 
    function($scope, principal)
{
  $scope.principal = principal;
});

แสดงหรือซ่อนองค์ประกอบ:

<div ng-show="principal.isAuthenticated()">
   I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
  I'm not logged in
</div>

ฯลฯ เป็นต้นไปเรื่อย ๆ อย่างไรก็ตามในแอปตัวอย่างของคุณคุณจะมีสถานะสำหรับโฮมเพจที่จะอนุญาตให้ผู้ใช้ที่ไม่ผ่านการตรวจสอบสิทธิ์ลดลง พวกเขาอาจมีลิงก์ไปยังสถานะการลงชื่อเข้าใช้หรือสถานะการลงชื่อสมัครใช้หรือมีแบบฟอร์มเหล่านั้นอยู่ในหน้านั้น อะไรก็ตามที่เหมาะกับคุณ

หน้าแดชบอร์ดสามารถสืบทอดจากสถานะที่ต้องการให้ผู้ใช้เข้าสู่ระบบและพูดว่าเป็นUserสมาชิกบทบาท สิ่งที่ได้รับอนุญาตทั้งหมดที่เรากล่าวถึงจะมาจากที่นั่น


28
ขอบคุณสิ่งนี้ช่วยให้ฉันได้รหัสของฉันมารวมกัน สังเกตด้านบนถ้าคุณได้รับการกำหนดเส้นทางวง จำกัด (UI Router ข้อผิดพลาด) ลองแทน$location.path $state.go
jvannistelrooy

2
นี่เป็นคำตอบที่ยอดเยี่ยมและช่วยฉันได้มาก เมื่อฉันตั้งค่า user = principal ในคอนโทรลเลอร์ของฉันและพยายามโทรบอกว่า user.identity (). ชื่อในมุมมองของฉันเพื่อรับชื่อผู้ใช้ที่เข้าสู่ระบบในปัจจุบันชื่อฉันดูเหมือนจะได้รับวัตถุสัญญา {แล้ว: fn, catch: fn ในที่สุด :} ส่งคืนและไม่ใช่วัตถุ _identity จริง ถ้าฉันใช้ user.identity. แล้ว (fn (ผู้ใช้)) ฉันสามารถรับวัตถุผู้ใช้ แต่ดูเหมือนว่ารหัสจำนวนมากสำหรับการดูฉันไม่มีอะไร?
Mark

4
@ Ir1sh ฉันจะแก้ไขตัวตนแรกในตัวควบคุมและกำหนดให้กับ$scope.userในthenฟังก์ชั่นของคุณ คุณยังสามารถอ้างอิงuserในมุมมองของคุณ เมื่อได้รับการแก้ไขมุมมองจะถูกปรับปรุง
moribvndvs

2
@HackedByChinese ฉันคิดว่าการสาธิตของคุณไม่ทำงานอีกต่อไป
Blowsie

7
@ jvannistelrooy ฉันมีปัญหากับ go () ถึง แต่หลังจากวางไว้ภายในแล้วหลังจากเรียกฟังก์ชัน noop เช่นนี้$q.when(angular.noop).then(function(){$state.go('myState')ทุกอย่างทำงานตามที่คาดไว้ หากฉันโทร$state.goในขณะที่การเปลี่ยนสถานะอื่นยังไม่เสร็จสมบูรณ์มันจะไม่ทำงาน (ฉันคิดว่านั่นเป็นสาเหตุที่ทำให้ไม่สามารถทำงานได้)
เซบาสเตียน

120

โซลูชันที่โพสต์จนถึงตอนนี้มีความซับซ้อนโดยไม่จำเป็น มีวิธีที่ง่ายกว่า เอกสารui-routerกล่าวว่าการฟัง$locationChangeSuccessและการใช้งาน$urlRouter.sync()ในการตรวจสอบการเปลี่ยนแปลงของรัฐหยุดมันหรือดำเนินการได้ แต่ถึงกระนั้นมันก็ใช้งานไม่ได้

อย่างไรก็ตามนี่เป็นสองทางเลือกง่ายๆ เลือกหนึ่ง:

โซลูชันที่ 1: กำลังฟัง $locationChangeSuccess

คุณสามารถฟัง$locationChangeSuccessและคุณสามารถดำเนินการตรรกะบางอย่างแม้ตรรกะแบบอะซิงโครนัสที่นั่น ขึ้นอยู่กับตรรกะนั้นคุณสามารถปล่อยให้ฟังก์ชันส่งคืนไม่ได้กำหนดซึ่งจะทำให้การเปลี่ยนสถานะดำเนินต่อไปตามปกติหรือคุณสามารถทำได้$state.go('logInPage')หากผู้ใช้จำเป็นต้องได้รับการตรวจสอบความถูกต้อง นี่คือตัวอย่าง:

angular.module('App', ['ui.router'])

// In the run phase of your Angular application  
.run(function($rootScope, user, $state) {

  // Listen to '$locationChangeSuccess', not '$stateChangeStart'
  $rootScope.$on('$locationChangeSuccess', function() {
    user
      .logIn()
      .catch(function() {
        // log-in promise failed. Redirect to log-in page.
        $state.go('logInPage')
      })
  })
})

โปรดทราบว่าสิ่งนี้ไม่ได้ป้องกันสถานะเป้าหมายจากการโหลด แต่จะเปลี่ยนเส้นทางไปยังหน้าลงชื่อเข้าใช้หากผู้ใช้ไม่ได้รับอนุญาต ไม่เป็นไรเนื่องจากการป้องกันที่แท้จริงอยู่บนเซิร์ฟเวอร์อยู่แล้ว

โซลูชันที่ 2: ใช้สถานะ resolve

ในการแก้ปัญหานี้คุณใช้คุณลักษณะการแก้ไขui-router

โดยทั่วไปคุณปฏิเสธสัญญาในresolveกรณีที่ผู้ใช้ไม่ได้รับการรับรองความถูกต้องแล้วเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ

นี่คือวิธี:

angular.module('App', ['ui.router'])

.config(
  function($stateProvider) {
    $stateProvider
      .state('logInPage', {
        url: '/logInPage',
        templateUrl: 'sections/logInPage.html',
        controller: 'logInPageCtrl',
      })
      .state('myProtectedContent', {
        url: '/myProtectedContent',
        templateUrl: 'sections/myProtectedContent.html',
        controller: 'myProtectedContentCtrl',
        resolve: { authenticate: authenticate }
      })
      .state('alsoProtectedContent', {
        url: '/alsoProtectedContent',
        templateUrl: 'sections/alsoProtectedContent.html',
        controller: 'alsoProtectedContentCtrl',
        resolve: { authenticate: authenticate }
      })

    function authenticate($q, user, $state, $timeout) {
      if (user.isAuthenticated()) {
        // Resolve the promise successfully
        return $q.when()
      } else {
        // The next bit of code is asynchronously tricky.

        $timeout(function() {
          // This code runs after the authentication promise has been rejected.
          // Go to the log-in page
          $state.go('logInPage')
        })

        // Reject the authentication promise to prevent the state from loading
        return $q.reject()
      }
    }
  }
)

ไม่เหมือนโซลูชันแรกโซลูชันนี้จะป้องกันไม่ให้สถานะเป้าหมายโหลดอย่างแท้จริง


6
@FredLackey state Aบอกว่าผู้ใช้ที่ไม่ได้รับอยู่ใน พวกเขาคลิกลิงก์ที่จะไปแต่คุณต้องการที่จะเปลี่ยนเส้นทางไปยังprotected state B logInPageถ้าไม่มี$timeout, ก็จะหยุดการเปลี่ยนสถานะทั้งหมดดังนั้นผู้ใช้จะอยู่ในการติดui-router ช่วยแรกป้องกันการเปลี่ยนแปลงเริ่มต้นเพราะการแก้ปัญหาที่ถูกปฏิเสธและหลังจากที่ทำก็เปลี่ยนเส้นทางไปยัง state A$timeoutui-routerprotected state BlogInPage
MK Safi

เป็นที่ที่authenticateฟังก์ชั่นที่เรียกว่าได้จริง?
CodyBugstein

@Imray ฟังก์ชั่นจะถูกส่งเป็นพารามิเตอร์ไปauthenticate ui-routerคุณไม่ต้องเรียกมันด้วยตัวเอง ui-routerเรียกมันว่า
MK Safi

ทำไมคุณถึงใช้ '$ locationChangeSuccess' แทนที่จะเป็น '$ stateChangeStart'
Draex_

@ PeterDraexDräxlerฉันส่วนใหญ่ทำตามเอกสาร คุณสังเกตเห็นความแตกต่างจากการใช้งาน$stateChangeStartหรือไม่?
MK Safi

42

ทางออกที่ง่ายที่สุดคือการใช้$stateChangeStartและevent.preventDefault()เพื่อยกเลิกการเปลี่ยนแปลงสถานะเมื่อผู้ใช้ไม่ได้รับการรับรองความถูกต้องและเปลี่ยนเส้นทางให้เขารับรองความถูกต้องของรัฐที่เป็นหน้าเข้าสู่ระบบ

angular
  .module('myApp', [
    'ui.router',
  ])
    .run(['$rootScope', 'User', '$state',
    function ($rootScope, User, $state) {
      $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        if (toState.name !== 'auth' && !User.authenticaded()) {
          event.preventDefault();
          $state.go('auth');
        }
      });
    }]
  );

6
ฉันไม่คิดว่าวิธีนี้จะใช้งานได้หาก User.authenticaded () เป็นการโทรแบบ async นั่นคือจอกศักดิ์สิทธิ์ที่ทุกคนรอคอย ตัวอย่างเช่นหากทุกรัฐยกเว้น "เข้าสู่ระบบ" มีความปลอดภัยฉันต้องการยืนยันว่าผู้ใช้ยังคงได้รับการรับรองความถูกต้องก่อนที่จะโหลดรัฐใด ๆ การใช้ตัวแก้ปัญหา sucks เพราะแก้ไขเพียงครั้งเดียวและเพื่อป้องกันไม่ให้โหลดสถานะเด็กคุณต้องฉีดการแก้ไขลงในเด็กทุกคน
Jason

การพิสูจน์ตัวตนไม่ใช่การเรียก async ในกรณีของฉัน: `this.authenticaded = function () {ถ้า (this.currentAccountID! == null) {return true; } คืนค่าเท็จ }; `
sebest

ตาม: stackoverflow.com/a/38374313/849829 'เรียกใช้' มาทางด้านบน 'บริการและด้วยเหตุนี้ปัญหา การตรวจสอบการจัดเก็บในพื้นที่สำหรับสถานะที่ผ่านการตรวจสอบแล้วน่าจะเป็นวิธีที่ดี
Deepak Thomas

22

ฉันคิดว่าคุณต้องมีserviceกระบวนการจัดการการรับรองความถูกต้อง (และที่เก็บข้อมูล)

ในบริการนี้คุณจะต้องมีวิธีการพื้นฐาน:

  • isAuthenticated()
  • login()
  • logout()
  • ฯลฯ ...

บริการนี้ควรถูกฉีดเข้าไปในคอนโทรลเลอร์ของคุณสำหรับแต่ละโมดูล:

  • ในส่วนแดชบอร์ดของคุณใช้บริการนี้เพื่อตรวจสอบว่าผู้ใช้รับรองความถูกต้อง ( service.isAuthenticated()วิธีการ) ถ้าไม่ใช่ให้เปลี่ยนเส้นทางไปที่ / เข้าสู่ระบบ
  • ในส่วนการเข้าสู่ระบบของคุณเพียงใช้ข้อมูลแบบฟอร์มในการตรวจสอบผู้ใช้ผ่านservice.login()วิธีการของคุณ

ตัวอย่างที่ดีและมีประสิทธิภาพสำหรับพฤติกรรมนี้คือแองกูลาร์แอพของโครงการและโดยเฉพาะโมดูลความปลอดภัยซึ่งยึดตามโมดูลHTTP Auth Interceptor ที่ยอดเยี่ยม

หวังว่านี่จะช่วยได้


21

ฉันสร้างโมดูลนี้ขึ้นเพื่อช่วยในการทำเค้กชิ้นนี้

คุณสามารถทำสิ่งต่าง ๆ เช่น:

$routeProvider
  .state('secret',
    {
      ...
      permissions: {
        only: ['admin', 'god']
      }
    });

หรือยัง

$routeProvider
  .state('userpanel',
    {
      ...
      permissions: {
        except: ['not-logged-in']
      }
    });

มันเป็นแบรนด์ใหม่ แต่คุ้มค่าการตรวจสอบ!

https://github.com/Narzerus/angular-permission


2
อะไรที่ทำให้ฉันไม่สามารถแก้ไขแหล่งที่มาขณะใช้งานจริงและลบ 'admin' ของคุณออก | | 'พระเจ้า' และดำเนินการต่อ?
Pogrindis

12
ฉันหวังว่าการร้องขอข้อมูลใด ๆ ที่ต้องได้รับอนุญาตจะได้รับการตรวจสอบที่เซิร์ฟเวอร์เช่นกัน
Ben Ripley

24
สิ่งนี้ไม่ได้มีไว้เพื่อความปลอดภัยการให้สิทธิ์ด้านลูกค้าจะไม่เกิดขึ้นเพราะคุณสามารถเปลี่ยนค่าได้เสมอ คุณสามารถดักจับการตอบสนองจากฝั่งเซิร์ฟเวอร์และประเมินว่าเป็น "ผู้มีอำนาจ" จุดอนุญาต / การอนุญาตด้านลูกค้าคือการหลีกเลี่ยงการอนุญาตให้ผู้ใช้ทำสิ่งต้องห้ามเพื่อวัตถุประสงค์ ux ตัวอย่างเช่นหากคุณกำลังจัดการกับการกระทำของผู้ดูแลระบบเท่านั้นแม้ว่าผู้ใช้จะหลอกลูกค้าให้อนุญาตการส่งคำขอที่ จำกัด ไปยังเซิร์ฟเวอร์เซิร์ฟเวอร์จะยังคงส่งคืนการตอบสนอง 401 แน่นอนว่านี่เป็นความรับผิดชอบของ api ที่ถูกนำมาใช้อย่างแน่นอน @BenRipley แน่นอน
Rafael Vidaurre

3
ตอบสนองต่อคำถาม Rafael อย่างยอดเยี่ยม ปกป้อง api เสมอเพราะส่วนหน้าเป็นสิ่งที่สามารถทำวิศวกรรมย้อนกลับได้มากที่สุดและมีการปลอมแปลงได้เกือบ
Frankie Loscavio

1
ปัญหาเกี่ยวกับประวัตินี้ได้รับการแก้ไขมาระยะหนึ่งแล้ว @Bohdan คุณสามารถใช้งานได้อย่างปลอดภัยแม้กับอุปกรณ์เสริม ui-router
masterspambot

16

ฉันต้องการแบ่งปันวิธีแก้ปัญหาอื่นที่ทำงานกับ ui เราเตอร์ 1.0.0.X

ดังที่คุณอาจทราบแล้ว stateChangeStart และ stateChangeSuccess จะเลิกใช้แล้ว https://github.com/angular-ui/ui-router/issues/2655

แต่คุณควรใช้การเปลี่ยน $ $ เป็นรูปสามเหลี่ยม

นี่คือวิธีที่ฉันได้รับ:

ก่อนอื่นฉันมีและAuthServiceด้วยฟังก์ชันที่มีประโยชน์

angular.module('myApp')

        .factory('AuthService',
                ['$http', '$cookies', '$rootScope',
                    function ($http, $cookies, $rootScope) {
                        var service = {};

                        // Authenticates throug a rest service
                        service.authenticate = function (username, password, callback) {

                            $http.post('api/login', {username: username, password: password})
                                    .success(function (response) {
                                        callback(response);
                                    });
                        };

                        // Creates a cookie and set the Authorization header
                        service.setCredentials = function (response) {
                            $rootScope.globals = response.token;

                            $http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token;
                            $cookies.put('globals', $rootScope.globals);
                        };

                        // Checks if it's authenticated
                        service.isAuthenticated = function() {
                            return !($cookies.get('globals') === undefined);
                        };

                        // Clear credentials when logout
                        service.clearCredentials = function () {
                            $rootScope.globals = undefined;
                            $cookies.remove('globals');
                            $http.defaults.headers.common.Authorization = 'Bearer ';
                        };

                        return service;
                    }]);

จากนั้นฉันมีการกำหนดค่านี้:

angular.module('myApp', [
    'ui.router',
    'ngCookies'
])
        .config(['$stateProvider', '$urlRouterProvider',
            function ($stateProvider, $urlRouterProvider) {
                $urlRouterProvider.otherwise('/resumen');
                $stateProvider
                        .state("dashboard", {
                            url: "/dashboard",
                            templateUrl: "partials/dashboard.html",
                            controller: "dashCtrl",
                            data: {
                                authRequired: true
                            }
                        })
                        .state("login", {
                            url: "/login",
                            templateUrl: "partials/login.html",
                            controller: "loginController"
                        })
            }])

        .run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
            function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {

                // keep user logged in after page refresh
                $rootScope.globals = $cookies.get('globals') || {};
                $http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;

                $transitions.onStart({
                    to: function (state) {
                        return state.data != null && state.data.authRequired === true;
                    }
                }, function () {
                    if (!AuthService.isAuthenticated()) {
                        return $state.target("login");
                    }
                });
            }]);

คุณจะเห็นว่าฉันใช้

data: {
   authRequired: true
}

เพื่อทำเครื่องหมายสถานะที่สามารถเข้าถึงได้หากได้รับการรับรองความถูกต้องแล้วเท่านั้น

จากนั้นใน. รันฉันจะใช้ช่วงการเปลี่ยนภาพเพื่อตรวจสอบสถานะอัตโนมัติ

$transitions.onStart({
    to: function (state) {
        return state.data != null && state.data.authRequired === true;
    }
}, function () {
    if (!AuthService.isAuthenticated()) {
        return $state.target("login");
    }
});

ฉันสร้างตัวอย่างนี้โดยใช้โค้ดบางอย่างที่พบในเอกสารประกอบ $ transitions ฉันค่อนข้างใหม่กับเราเตอร์ ui แต่ใช้งานได้

หวังว่ามันจะช่วยให้ทุกคน


สิ่งนี้ยอดเยี่ยมสำหรับผู้ที่ใช้เราเตอร์รุ่นใหม่ ขอบคุณ!
mtro

5

นี่คือวิธีที่เราออกจากลูปการวนรอบแบบไม่มีที่สิ้นสุดและยังคงใช้$state.goแทน$location.path

if('401' !== toState.name) {
  if (principal.isIdentityResolved()) authorization.authorize();
}

1
ใครบ้างรู้ว่าทำไมเมื่อใช้คำตอบ / การตั้งค่าที่ได้รับการยอมรับซึ่งอธิบายไว้ด้านบนแถบที่อยู่จะไม่แสดง URL และแฟรกเมนต์และพารามิเตอร์สตริงการสืบค้นอีกต่อไป? ตั้งแต่การใช้งานแถบที่อยู่นี้ไม่อนุญาตให้แอปของเราถูกบุ๊กมาร์กอีกต่อไป
Frankie Loscavio

1
สิ่งนี้ไม่ควรจะเป็นความเห็นต่อหนึ่งในคำตอบที่มีอยู่หรือไม่ เนื่องจากไม่มีรหัสดังกล่าวใน OP และยังไม่ชัดเจนว่าคำตอบใด / รหัสนี้หมายถึงอะไร
TJ

3

ฉันมีวิธีแก้ไขปัญหาอื่น: โซลูชันนั้นทำงานได้อย่างสมบูรณ์แบบเมื่อคุณมีเนื้อหาที่คุณต้องการแสดงเมื่อล็อกอินเท่านั้นกำหนดกฎที่คุณตรวจสอบว่าคุณลงชื่อเข้าใช้หรือไม่และเส้นทางไม่ใช่เส้นทางของรายการที่อนุญาต

$urlRouterProvider.rule(function ($injector, $location) {
   var UserService = $injector.get('UserService');
   var path = $location.path(), normalized = path.toLowerCase();

   if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
     $location.path('/login/signin');
   }
});

ในตัวอย่างของฉันฉันถามว่าฉันไม่ได้เข้าสู่ระบบและเส้นทางปัจจุบันที่ฉันต้องการเส้นทางไม่ได้เป็นส่วนหนึ่งของ `/ เข้าสู่ระบบ 'เพราะเส้นทางที่ได้รับอนุญาตของฉันมีดังต่อไปนี้

/login/signup // registering new user
/login/signin // login to app

ดังนั้นฉันจึงสามารถเข้าถึงสองเส้นทางนี้ได้ทันทีและเส้นทางอื่น ๆ จะถูกตรวจสอบหากคุณออนไลน์

นี่คือไฟล์เส้นทางทั้งหมดของฉันสำหรับโมดูลเข้าสู่ระบบ

export default (
  $stateProvider,
  $locationProvider,
  $urlRouterProvider
) => {

  $stateProvider.state('login', {
    parent: 'app',
    url: '/login',
    abstract: true,
    template: '<ui-view></ui-view>'
  })

  $stateProvider.state('signin', {
    parent: 'login',
    url: '/signin',
    template: '<login-signin-directive></login-signin-directive>'
  });

  $stateProvider.state('lock', {
    parent: 'login',
    url: '/lock',
    template: '<login-lock-directive></login-lock-directive>'
  });

  $stateProvider.state('signup', {
    parent: 'login',
    url: '/signup',
    template: '<login-signup-directive></login-signup-directive>'
  });

  $urlRouterProvider.rule(function ($injector, $location) {
    var UserService = $injector.get('UserService');
    var path = $location.path();

    if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
         $location.path('/login/signin');
    }
  });

  $urlRouterProvider.otherwise('/error/not-found');
}

() => { /* code */ } เป็นไวยากรณ์ ES6 ให้ใช้แทน function() { /* code */ }


3

ใช้ Interceptor $ http

ด้วยการใช้ interceptor $ http คุณสามารถส่งส่วนหัวไปยัง Back-end หรือวิธีอื่น ๆ และทำการตรวจสอบด้วยวิธีดังกล่าว

บทความที่ยอดเยี่ยมเกี่ยวกับตัวสกัดกั้น $ http

ตัวอย่าง:

$httpProvider.interceptors.push(function ($q) {
        return {
            'response': function (response) {

                // TODO Create check for user authentication. With every request send "headers" or do some other check
                return response;
            },
            'responseError': function (reject) {

                // Forbidden
                if(reject.status == 403) {
                    console.log('This page is forbidden.');
                    window.location = '/';
                // Unauthorized
                } else if(reject.status == 401) {
                    console.log("You're not authorized to view this page.");
                    window.location = '/';
                }

                return $q.reject(reject);
            }
        };
    });

วางสิ่งนี้ลงในฟังก์ชัน. config หรือ. run


2

ก่อนอื่นคุณต้องมีบริการที่คุณสามารถฉีดเข้าไปในคอนโทรลเลอร์ที่มีแนวคิดเกี่ยวกับสถานะการตรวจสอบสิทธิ์ของแอพ การยืนยันรายละเอียดการตรวจสอบกับที่เก็บข้อมูลในเครื่องเป็นวิธีที่เหมาะสมในการเข้าถึง

ถัดไปคุณจะต้องตรวจสอบสถานะการตรวจสอบสิทธิ์ก่อนการเปลี่ยนแปลงสถานะ เนื่องจากแอปของคุณมีบางหน้าที่ต้องได้รับการรับรองความถูกต้องและอื่น ๆ ที่ไม่มีให้สร้างเส้นทางหลักที่ตรวจสอบการรับรองความถูกต้องและสร้างหน้าอื่น ๆ ทั้งหมดที่ต้องการเหมือนกันกับลูกของผู้ปกครองนั้น

สุดท้ายคุณจะต้องมีวิธีที่จะบอกว่าผู้ใช้ที่เข้าสู่ระบบในปัจจุบันสามารถดำเนินการบางอย่างได้หรือไม่ สิ่งนี้สามารถทำได้โดยการเพิ่มฟังก์ชั่น 'สามารถ' ในบริการรับรองความถูกต้องของคุณ สามารถใช้พารามิเตอร์สองตัว: - การกระทำ - จำเป็น - (เช่น 'manage_dashboards' หรือ 'create_new_dashboard') - วัตถุ - ตัวเลือก - วัตถุที่ดำเนินการอยู่ ตัวอย่างเช่นถ้าคุณมีวัตถุแดชบอร์ดคุณอาจต้องการตรวจสอบเพื่อดูว่า dashboard.ownerId === logInUser.id (แน่นอนข้อมูลที่ส่งผ่านจากลูกค้าไม่ควรเชื่อถือได้และคุณควรตรวจสอบสิ่งนี้บนเซิร์ฟเวอร์ก่อนที่จะเขียนลงในฐานข้อมูลของคุณ)

angular.module('myApp', ['ngStorage']).config([
   '$stateProvider',
function(
   $stateProvider
) {
   $stateProvider
     .state('home', {...}) //not authed
     .state('sign-up', {...}) //not authed
     .state('login', {...}) //not authed
     .state('authed', {...}) //authed, make all authed states children
     .state('authed.dashboard', {...})
}])
.service('context', [
   '$localStorage',
function(
   $localStorage
) {
   var _user = $localStorage.get('user');
   return {
      getUser: function() {
         return _user;
      },
      authed: function() {
         return (_user !== null);
      },
      // server should return some kind of token so the app 
      // can continue to load authenticated content without having to
      // re-authenticate each time
      login: function() {
         return $http.post('/login.json').then(function(reply) {
            if (reply.authenticated === true) {
               $localStorage.set(_userKey, reply.user);
            }
         });
      },
      // this request should expire that token, rendering it useless
      // for requests outside of this session
      logout: function() {
         return $http.post('logout.json').then(function(reply) {
            if (reply.authenticated === true) {
               $localStorage.set(_userKey, reply.user);
            }
         });
      },
      can: function(action, object) {
         if (!this.authed()) {
            return false;
         }

         var user = this.getUser();

         if (user && user.type === 'admin') {
             return true;
         }

         switch(action) {
            case 'manage_dashboards':
               return (user.type === 'manager');
         }

         return false;


      }
   }
}])
.controller('AuthCtrl', [
   'context', 
   '$scope', 
function(
   context, 
   $scope
) {
   $scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
      //only require auth if we're moving to another authed page
      if (toState && toState.name.indexOf('authed') > -1) {
         requireAuth();
      }
   });

   function requireAuth() {
      if (!context.authed()) {
         $state.go('login');
      }
   }
}]

** การปฏิเสธความรับผิด: รหัสข้างต้นเป็นรหัสหลอกและมาพร้อมกับไม่มีการรับประกัน **

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.