XIANFESchool / FE-problem-collection

前端问题收集和知识经验总结
https://github.com/ShuyunXIANFESchool/FE-problem-collection/issues
63 stars 22 forks source link

Angular中的安全性验证 #23

Open mmmaming opened 8 years ago

mmmaming commented 8 years ago

在开发的过程中,遇到身份验证的问题,如果在不登录的情况下直接访问项目的地址,怎么对用户的身份进行验证?常见的处理是:如果没有读取到用户的登录信息,则页面自动转向登录页面,那么作为开发人员,怎么实现此需求?目前据我所知的解决方案有两种。第一种是用ui-router提供的$stateChangeStart对于发生路由转换时进行判断,第二种则是用angular自带的拦截器进行处理。 先说第一种:$stateChangeStart 由于ui-router关心的是状态,所以我们可以在每次模板引擎被解析前触发此方法

 // 路由改变时判断是否登录,未登录则跳转至登录页面
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
        // 这里用event.preventDefault()可以阻止模板解析
        if (toState.name !== 'login' || fromState.name === 'login') {
             // 这里做身份验证的判断
            //  如判断cookie,session等。
           //  若判断不通过,则跳转至login页面
        }
 });

其中toState和fromState参数是获取路由的前一个状态和下一个状态,当发生路由状态改变时,如果访问的不是登陆页面或者不是从登陆页面跳转时,执行身份判断。 但是这种方法是有缺陷的,1.用到了$rootScope. 2.基于ui-router.3.不稳定(项目中测试发现).由于问题的存在,所以可以采用第二种拦截器的方法解决这些问题。

拦截器 Angular Interceptor 在与后台交互的过程中,有时会希望俘获一些请求,在其发送到服务端之前进行操作,或者在服务器完成响应执行调用前处理,拦截器就是为此应运而生的一种方法。 $httpProvider 中有一个 interceptors 数组,而所谓拦截器只是一个简单的注册到了该数组中的常规服务工厂。拦截器提供了四个方法:request , requestError ,response 和responseError 来对请求和响应进行处理。具体介绍看这里: 要实现身份验证,可以在每次$http请求到后台之前进行验证

function requestInterceptor($cookies,) {
    var requestConfig = {
        request: function(config) {
             // 这里做身份验证的判断
            //  如判断cookie,session等。
           //  若判断不通过,则跳转至login页面
            return config;
        }
    };
        return requestConfig;
}

该方法接收请求配置对象作为参数,然后必须返回配置对象或者 promise 。如果返回无效的配置对象或者 promise 则会被拒绝,导致 $http 调用失败。然后只需将创建的拦截器注册到$httpProvider的interceptors数组中即可。

module.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('myInterceptor');
}]);

在每次发送请求前,拦截器都会执行我们事先写好的判断代码,去做身份的验证。比stateChangeStart好的是,它在每次请求前都进行验证,而不只是对于路由切换时才验证。

拦截器可以做的事情还有很多,如请求恢复、loading等,搭配request , requestError ,response 和responseError实现即可,这里不做赘述。

fnjoe commented 8 years ago

项目中,两者都用还是选择一种?会不会逻辑重复

mmmaming commented 8 years ago

@fnjoe 第二种的好处是,每次发起任何请求都会进行验证,这样避免了一种情况就是,当身份过期时,在当前页面发出请求不会被验证。如果用第一种就会有这种风险。

fnjoe commented 8 years ago

@mmmaming 可以这样理解不,服务器将身份标识储存在session或者客户端的cookie里,每次请求都会携带cookie,然后在服务器端进行身份验证,前端使用http拦截器对相应的状态码做出反馈。

ng原生中有$locationChangeStart等监测url变化的事件,ngRoute中也有$routeChangeStart的事件,都是向下广播的,所有的子节点都可以注册监听函数。刚试了下,$stateChangeStart也是可以直接在子作用域中使用的。 不知道注册在$rootScope上是不是可以在第一时间进行权限的验证,从而选择是否阻止模板解析。

路由的权限验证可以考虑放置在路由的resolve中来进行,这样每个路由都拥有单独的安全验证。似乎和你们项目里统一处理路由验证的思路是反的,-_-,想想之后ng项目的集成时,说不定会用到。 不过无论ui-router还是ngRoute,所有基于路由的设计都会有resolve属性来进行路有成功前的处理。

hjzheng commented 8 years ago

补充一下,关于 AngularJS 拦截器一些有意思的用法

  1. 在request header中加入认证信息
  2. 处理服务器端异常状态
(function() {
    angular.module('app').config(config);
    config.$inject = ['$httpProvider', 'signProvider'];
    function config($httpProvider, signProvider) {
        $httpProvider.interceptors.push(['$rootScope', function($rootScope) {
            return {
                'request': function(config) {
                    var signs = signProvider.getSigns(config.url);
                    config.headers['access_token'] = signs.token;
                    config.headers['access_timestamp'] = signs.time;
                    return config;
                },
                'responseError': function(rejection) {
                    switch (rejection.status) {
                    case 401:
                        if (rejection.config.url !== 'api/login')
                            // If we're not on the login page
                            $rootScope.$broadcast('auth:loginRequired');
                        break;
                    case 403:
                        $rootScope.$broadcast('auth:forbidden');
                        break;
                    case 404:
                        $rootScope.$broadcast('page:notFound');
                        break;
                    case 500:
                        $rootScope.$broadcast('server:error');
                        break;
                    }
                    return rejection;
                }
            };
        }
        ]);
    }
})();