简体   繁体   English

使用有角度的前端处理身份验证->如何保护特定页面?

[英]Handling authentication with angular front-end -> How to protect specific pages?

Looking to build web app in Node.js with ability for user to log in (authentication), which has 3 non secure pages (/home, /contact, /about) and one secure page (/admin). 希望在Node.js中构建具有用户登录(身份验证)功能的Web应用程序,该应用程序具有3个非安全页面(/ home,/ contact,/ about)和一个安全页面(/ admin)。 As an aside, I've been referencing the scotch.io Mean Machine book. 顺便说一句,我一直在参考scotch.io Mean Machine书。

The issue I'm having is that I've build everything out, and the login mechanism works in that when I log in, I get directed to /admin; 我遇到的问题是我已经将所有内容都构建好了,登录机制的工作原理是:登录时,我被定向到/ admin; however, when I go to /admin in the URL without logging in, I can still access the page. 但是,当我不登录而进入URL中的/ admin时,仍可以访问该页面。 Ie I'm not sure where to put the actual protection. 也就是说,我不确定将实际保护放在何处。

A bit below on how I've laid out my app. 下面是我如何布置应用程序的信息。 Hoping for as much a conceptual answer to suggest how I should be doing things, rather than necessarily only a code answer. 希望有一个概念上的答案来建议我应该如何做,而不是仅是代码答案。

Services: 服务:

  • auth service posts to server the inputted username/password and returns either false or success (with user info and JWT token) auth服务将输入的用户名/密码发布到服务器,并返回false或成功(带有用户信息和JWT令牌)
  • auth service also injects as AuthInterceptor the token (if there is one) into each HTTP header auth服务还将令牌(如果有)作为AuthInterceptor注入到每个HTTP标头中

Router: 路由器:

angular.module('routerRoutes', ['ngRoute'])
    .config(function($routeProvider, $locationProvider) {
        $routeProvider
            .when('/', {
                templateUrl:    'views/home.html',
                controller:     'homeController',
                controllerAs:   'home'
            })
            .when('/about', {
                templateUrl:    'views/about.html',
                controller:     'aboutController',
                controllerAs:   'about'
            })
            .when('/contact', {
                templateUrl:    'views/contact.html',
                controller:     'contactController',
                controllerAs:   'contact'
            })
            .when('/login', {
                templateUrl:    'views/login.html',
                controller:     'adminController',
                controllerAs:   'login'
            })
            .when('/admin', {
                templateUrl:    'views/admin/admin.html',
                controller:     'adminController',
                controllerAs:   'admin'
            });

        $locationProvider.html5Mode(true);

    });

Controllers: 控制器:

  • homeController, aboutController, contactController are generally empty for now homeController,aboutController和contactController通常现在为空

  • adminController: adminController:

    .controller('adminController', function($rootScope, $location, Auth) { .controller('adminController',function($ rootScope,$ location,Auth){

     var vm = this; vm.loggedIn = Auth.isLoggedIn(); $rootScope.$on('$routeChangeStart', function() { vm.loggedIn = Auth.isLoggedIn(); window.alert(vm.loggedIn); // this gives correct answer and works Auth.getUser() .success(function(data) { vm.user = data; }); }); vm.doLogin = function() { vm.error = ''; Auth.login(vm.loginData.username, vm.loginData.password) .success(function(data) { vm.user = data.username; if (data.success) $location.path('/admin'); else vm.error = data.message; }); }; vm.doLogout = function() { Auth.logout(); vm.user = {}; $location.path('/login'); }; 

    }); });

And finally, below is my index.html (just the body): 最后,下面是我的index.html(仅是正文):

<body class="container" ng-app="meanApp" ng-controller="adminController as admin">

    <a href="/"><i class="fa fa-home">Home </i></a>
    <a href="/about"><i class="fa fa-shield">About </i></a>
    <a href="/contact"><i class="fa fa-comment">Contact</i></a>
    <a href="/admin"><i class="fa fa-comment">Admin</i></a>

    <ul class="nav navbar-nav navbar-right">
        <li ng-if="!admin.loggedIn"><a href="/login">Login</a></li>
        <li ng-if="admin.loggedIn" class="navbar-text">Hello {{ admin.user.username }}</li>
        <li ng-if="admin.loggedIn"><a href="#" ng-click="admin.doLogout()">Logout</a></li>
    </ul>

    <main>
        <div ng-view>

        </div>
    </main>

</body>

I won't paste the other html pages that get injected into since there isn't anything on them yet (the login.html has just the two input fields and submit button). 我将不会粘贴注入的其他html页面,因为它们上还没有任何东西(login.html只有两个输入字段和提交按钮)。

So a couple of questions: 有几个问题:

  • In my index.html, when I click on /admin, it takes me to the admin page even if I'm not logged in. Where should I put the protection for that to not happen? 在我的index.html中,当我单击/ admin时,即使我没有登录,它也带我进入管理页面。我应该在哪里防止这种情况发生?
  • Any general comments on my setup and best practices that I'm not following? 对我未遵循的设置和最佳做法有任何一般性评论吗?

Another nit: 另一个优点:

  • I read that "li ng-if=" wouldn't show up in 'view source' if that branch of the decision tree wasn't hit, but it does. 我读到,如果未命中决策树的该分支,则不会在“查看源代码”中显示“ li ng-if =“,但确实如此。 Was I misled or am I doing something wrong? 我被误导了还是做错了什么?

I took a custom property route to secure the routes in my application. 我采用了自定义属性路由来保护应用程序中的路由。 Every state change taking place is listened for and inspected if it has this property. 侦听并检查每个发生的状态更改是否具有此属性。 If it has this property set then it checks if user is logged in, if they are not, it routes them to the 'login' state. 如果设置了此属性,则它将检查用户是否已登录;如果未登录,则会将其路由到“登录”状态。

I used UI-ROUTER in my current project where I have implemented this. 我在实现该功能的当前项目中使用了UI-ROUTER。 I made a custom parameter called "data" that I used within the route. 我在路由中使用了一个自定义参数“ data”。

Within a .config block to declare my opening routes: 在.config块中声明我的开放路线:

      $stateProvider
         .state('login', {
             url: '/login',
             templateUrl: 'login/login.html',
             controller: 'LoginController',
             controllerAs: 'vm'
          })
         .state('home', {
             url: '',
             templateUrl: 'layout/shell.html',
             controller: 'ShellController',
             controllerAs: 'vm',
             data: {
                 requireLogin: true
             }
          })

Then I add this to a .run on the application where I'm looking for ui-router's $stateChangeStart event and looking at my custom property ('data') on the state declaration: 然后,将其添加到应用程序中的.run,在其中查找ui路由器的$ stateChangeStart事件并在状态声明中查看我的自定义属性(“数据”):

     $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {

        var requireLogin = toState.hasOwnProperty('data') && toState.data.requireLogin;
        if (requireLogin && !authService.isLoggedIn()) {
            event.preventDefault();
            authService.setDestinationState(toState.name);
            $state.go('login');
        }

        if (toState.name !== 'login') {
            authService.setDestinationState(toState.name);
        }
    });

In case you're wondering what the authService.setDestinationState does... it preserves the URL that the user was attempting to visit... once they successfully login it forwards them to that state automagically (see below): 如果您想知道authService.setDestinationState的功能是什么...它将保留用户尝试访问的URL ...一旦成功登录,它将自动将其转发到该状态(请参见下文):

     function login() {
        authService.authLogin(vm.credentials)
            .then(loginComplete)
            .catch(loginFailed);

        function loginComplete(data, status, headers, config) {
            vm.user = data;
            $rootScope.$broadcast('authorized');
            $state.go(authService.getDestinationState());
        }

        function loginFailed(status) {
            console.log('XHR Failed for login.');
            vm.user = undefined;
            vm.error = 'Error: Invalid user or password. ' + status.error;
            toastr.error(vm.error, {closeButton: true} );
        }
    }

When you define your Admin route, you can define a property called resolve. 定义管理路由时,可以定义一个名为resolve的属性。 Each property within resolve is a function (it can be an injectable function). resolve中的每个属性都是一个函数(可以是可注入函数)。 This function should return a promise, the promise's result can be injected into the controller. 这个函数应该返回一个promise,promise的结果可以注入到控制器中。

For more information on resolve, look at http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx . 有关解决的更多信息,请参见http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx

You can use resolve as follows to do an authentication check. 您可以按以下方式使用resolve进行身份验证检查。

var authenticateRoute = ['$q', '$http' , function ($q, $http) {
    var deferred = $q.defer();
    $http.get("http://api.domain.com/whoami")
         .then(function(response) { 
                   if (response.data.userId) deferred.resolve(response.data.userId);  
                   else window.location.href = "#/Login"
               });
    return deferred.promise();
}]

// ...

.when('/admin', {
    templateUrl:    'views/admin/admin.html',
    controller:     'adminController',
    controllerAs:   'admin',
    resolve: {
        authenticatedAs: authenticateRoute
    }
});

With this you could pass the authenticated User Id through - even if null - and let the controller deal with it, if for instance, you want a contextual message. 这样,您可以传递经过身份验证的用户ID(即使为null),也可以让控制器处理它,例如,如果您需要上下文消息。 Else, you could do as above and only do so if there is a user Id from the authentication request, otherwise redirect to your login route. 否则,您可以执行上述操作,并且仅在身份验证请求中有用户ID时才这样做,否则重定向到您的登录路由。

Hope this helps! 希望这可以帮助! /AJ / AJ

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM