简单的登录与权限(Java + Angularjs)

数据库模型

简单的用户权限系统中, 涉及到三种模型: 用户、角色、权限。 用户具有角色, 角色拥有权限, 于是用户通过角色便可以拥有权限。基于这样的联系, 用户通过角色便获得了权限。如图。著名的开源系统Redmine就使用了这种简单的权限设计。

User -> RoleList -> PermissionList

服务器端

登录机制

三个接口: 用户登录, 用户登出, 用户是否登录。此处过于基础常见, 不再细说。

权限机制

通过注解管理接口权限, 定义权限注解

[java]
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
     String value();  //接口权限
}
[/java]

需权限限制方法处添加注记:

[java]
@RestController
Class A {
@Permission(value=’create’)
String create() {
… // create logic
}
}
[/java]

通过拦截器实现权限限制逻辑

[java]
public class PermissionInterceptor extends HandlerInterceptorAdapter {

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


List<String> userPermissionsList = …; //从request中获取用户权限列表

if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
Permission permissionAnno = ((HandlerMethod) handler).getMethodAnnotation(Permission.class);
// 从注解中获取接口允许的权限列表
String permission = permissionAnno.value();
// 如果用户权限列表于接口权限列表有交集, 放行; 否则返回客户端无权限
if (StringUtils.isBlank(permission) || userPermissionsList.contains(permission)) {
return true;
} else {
Return false;
}
}
return true;
}

[/java]

客户端(AngularJs)

登录机制

主要可以分为以下几块。

本地登录状态存储: 按照生命周期的不同, 可以保存为全局变量(页面级), SessionStorage(会话级), localStorage(本地持久化)。

服务器登录状态验证: 每次发送http请求, 或者切换页面时, 检查服务器端的登录状态。

登录状态更新事件的传播:每次获取到登录状态, 向顶层作用域广播登录状态的更新时间

登录状态改变事件的捕获:判断最新的登录状态, 如果没有登录, 跳转至登录页面。

本地登录信息存储

在AngularJs中 , Service都是单例的。所以我们可以把状态的保存放在Service中。 而根据实际需要, 登录状态以及登录用户信息可以在Service中用成员变量保存, 或者从sessionStorage中读取, 或者从localStorage中读取。

通过这个本地的登录状态, 系统可以判断当前的登录状态, 也可以读取登录用户的信息。

下面的这个例子中, 本地用户登录信息是放在成员变量中, 所以每次打开页面, 这个变量都是空, 系统都会跳转到登录页面。
[javascript]
app.factory(‘BaseAuthService’, function ($rootScope,$q, $http) {
var baseLoginUser = null;
var userName = "";
var userId = "";

return {
getAuth: function () {
return {
baseLoginUser : baseLoginUser,
userName : userName,
userId : userId
}
},
getUserId: function () {
return userId;
},
getUser: function () {
return baseLoginUser;
},
isLogin: function () {
if (null == baseLoginUser) {
return false;
} else {
return true;
}
},
};

});

[/javascript]

登录状态验证

在特定的操作下, 特定的时间点, 前端页面需要到服务器获取用户登录状态。 具体选择什么样的操作, 什么样的时机, 还是需要根据自己系统的特点选择。

如果为了实现简单, 系统也没有大的并发压力, 可以在每一次http请求, 每一次页面流转时, 均进行登录验证。也可以在获取到登录信息的同时, 获取登录信息的过期时间, 前端页面只有在登录信息过期时才试图更新登录信息。

现在只讨论如何在每一次http请求, 每一次页面流转时, 加入登录状态的验证。

  • http拦截器

每一次http请求后均检查用户是否登录, 如果检测到未登录, 设置系统状态为未登录。

[javascript]

app.config([‘$httpProvider’, function ($httpProvider){
$httpProvider.interceptors.push(‘normalHttpInterceptor’);
}]);

app.factory(‘normalHttpInterceptor’, [‘$rootScope’, ‘$injector’, function ($rootScope,$injector){
return {

response: function(res){
$rootScope.gHideLoading();
var BaseAuthService = $injector.get(‘BaseAuthService’);
if (!!res.data && res.data.code == "business021") {
// 未登录, 跳转登录页面
BaseAuthService.setAuth(null, null);
}
return res;
},
};
}]);

[/javascript]

  • 页面流转处登录验证
  • 捕获页面流转时产生的$stateChangeStart事件,如果成功获取用户的登录状态(已登录或未登录), 设置本地系统状态为相应状态。

    [javascript]
     app.run(["$rootScope",’$state’, ‘BaseAuthService’, function($rootScope, $state, BaseAuthService){
    $rootScope.$on(‘$stateChangeStart’, function(event, toState, toParams, fromState, fromParams) {

    if (toState.name == "login") {
    return;
    }
    var fetch = BaseAuthService.fetchAuth();
    fetch.then(
    function (res) {

    if (!!res) {
    if (res.success) {
    BaseAuthService.setAuth(res.data, res.data.permissions);
    } else {
    BaseAuthService.setAuth(null, null);
    }
    } else {

    }
    },
    function (err) {

    }
    );
    });

    }]);

    [/javascript]

    登录状态更新事件的传播

    更新本地的登录状态时, 同时广播登录状态更改事件: baseAuthChanged
    [javascript]
    app.factory(‘BaseAuthService’, function ($rootScope,$q, $http) {
    var basePermissionList = null;
    var baseLoginUser = null;
    var userName = "";
    var userId = "";

    …..
    return {
    setAuth: function(baseLoginUse, basePermissionLis) {
    baseLoginUser = baseLoginUse;
    basePermissionList = basePermissionLis;

    userName = null;
    userId = null;
    if (!!baseLoginUser) {
    userName = baseLoginUser.userName;
    userId = baseLoginUser.userId;
    }

    $rootScope.$broadcast(‘baseAuthChanged’);
    $rootScope.$broadcast(‘permissionsChanged’);
    },

    };

    });
    [/javascript]

    登录状态改变事件的捕获

    在最外层的Controller中监听事件“baseAuthChanged”
    事件触发时, 如果本地登录状态为未登录, 跳转到登录页面。

    [javascript]
    $rootScope.$on("baseAuthChanged", function () {
    var auth = BaseAuthService.getAuth();

    if (!BaseAuthService.isLogin()) {
    //$location.path("/login");
    $state.go(‘login’);
    return;
    }
    });

    [/javascript]

    AngularJs中两种事件传播机制

    $broadcast的作用是将事件从父级作用域传播至子级作用域,包括自己。格式如下:$broadcast(eventName,args)

    $emit的作用是将事件从子级作用域传播至父级作用域,包括自己,直至根作用域。格式如下:$emit(eventName,args)

    权限机制

    前端权限控制的目的, 仅仅是让元素按照不同的权限进行显示与隐藏。具体实现起来十分简单。

    在上述的登录机制中, 后台接口提供的登录用户信息, 不仅仅有id, 用户名等基本信息,还需提供权限列表信息。我们设置本地登录信息的时候, 登录用户的权限列表也就保存在本地页面了。

    [javascript]
    app.factory(‘BaseAuthService’, function ($rootScope,$q, $http) {
    var basePermissionList = null;
    var baseLoginUser = null;
    var userName = "";
    var userId = "";

    var fetchAuth = function () {
    var deferred = $q.defer();
    $http({
    method: ‘GET’,
    url: ‘rms/1.0/auth/getLoginUser’
    }).success(function(response) {
    deferred.resolve(response);
    }).error(function(response) {
    deferred.reject(response);
    });
    return deferred.promise;
    }

    return {
    setAuth: function(baseLoginUse, basePermissionLis) {
    baseLoginUser = baseLoginUse;
    basePermissionList = basePermissionLis;

    userName = null;
    userId = null;
    if (!!baseLoginUser) {
    userName = baseLoginUser.userName;
    userId = baseLoginUser.userId;
    }

    $rootScope.$broadcast(‘baseAuthChanged’);
    $rootScope.$broadcast(‘permissionsChanged’);
    },
    getAuth: function () {
    return {
    baseLoginUser : baseLoginUser,
    basePermissionList : basePermissionList,
    userName : userName,
    userId : userId
    }
    },
    getUserId: function () {
    return userId;
    },
    getUser: function () {
    return baseLoginUser;
    },
    getPermissions: function () {
    return basePermissionList;
    },
    hasPermission: function (permission) {
    if (null == permission || typeof (permission) == "undefined" || permission == "") {
    return true;
    }

    for (var idx in basePermissionList) {
    var userPermission = basePermissionList[idx];
    if (userPermission.name == permission) {
    return true;
    }
    }

    return false;
    },
    isLogin: function () {
    if (null == baseLoginUser) {
    return false;
    } else {
    return true;
    }
    },
    fetchAuth : fetchAuth
    };

    });

    [/javascript]

    在本地拥有了权限列表的前提下, 我们可以通过AngularJs的指令机制, 轻易地将权限应用到页面上的元素中。

    <button has-permission=”create”></button>

    [javascript]
    app.directive(‘hasPermission’, function(BaseAuthService) {
    return {
    restrict: ‘A’,
    link: function(scope, element, attrs) {

    var value = attrs.hasPermission.trim();
    var notPermissionFlag = value[0] === ‘!’;
    if(notPermissionFlag) {
    value = value.slice(1).trim();
    }

    function toggleVisibilityBasedOnPermission() {
    var hasPermission = BaseAuthService.hasPermission(value);

    if(hasPermission && !notPermissionFlag || !hasPermission && notPermissionFlag)
    element.show();
    else
    element.hide();
    }
    toggleVisibilityBasedOnPermission();
    scope.$on(‘permissionsChanged’, toggleVisibilityBasedOnPermission);
    }
    };
    });

    [/javascript]

    发表评论

    邮箱地址不会被公开。