如何设计一个的系统权限(用户,角色,权限,渠道授权【PC、PAD、PHONE、WAP】)

2017-01-11 15:24:35来源:oschina作者:小娃娃人点击


前言

随着移动互联网的发展,系统使用者从PC端移动到了移动端,如何控制权限系统成为了一个问题。现将JSUP【java2e source unify platform】权限设计思路写出,一来记录自己的程序开发之路,二来能给予大家帮助【理想状态哈】。


分析:

说到权限管理,大家肯定会想到很多,网络资源也很多,专业一点的如基于RBAC模型,但是对于想直接设计系统权限,还是很困难。简单一点儿的设计,便就是用户与权限,这样便能保证一个用户拥有怎么样的权限。使用频次比较低,用户使用数较少时,这种设计也可解决问题,方便快捷嘛。


假设一种场景,现公司有一套内容管理系统,如果公司里面所有员工都有这样的权限呢,每一个人都要配置?那是一件很痛苦的事情。因此再添加一个中间物--角色,把某些人归为一类,然后再把权限分配给角色。角色属下的用户也就拥有了权限。将这种场景可以举一反三很多。


这就是大家熟知的用户关联角色,角色管理权限。


所谓渠道授权,即给定某一渠道分配Appkey以及秘钥进行控制,并且可以限定某一渠道请求的服务次数。由于渠道控制并不是系统权限的核心,故以下描述中,不作为重点描述。


鉴于此,有了用户、角色、权限、渠道,那么应该考虑一下这几个概念之间的关系。


1、 一个用户可以拥有多个角色。多对多关系


2、 一个角色可以拥有多个权限。多对多关系


3、 一个用户可以在多个渠道使用。多对多关系【非重点】


不同的应用场合,你可能会想出不同的需求,提了一个新的需求以后,可能会发现原来的设计没方法实现了,其实不用考虑那么复杂,借用RBAC模式设计,其实问题很简单,无非就是其实就是Who、What、How的问题。


实现效果图:
系统登录

用户可以进行角色选择进入系统。效果图如下所示:



权限管理

JSUP平台内权限管理可以进行CURD操作,方便管理员实时维护,主要功能有查询、增加、修改、删除、关联。维护的粒度为按钮级别。关联功能主要可以用于流程性功能时使用。系统内所有操作均可以抽象为资源,如菜单资源、按钮资源、导航资源、系统级资源【无需授权即可使用】。效果图如下:



角色管理

角色可以理解为权限集合,JSUP内角色功能,也可以理解为角色组以及角色,角色之间从在父子关系。效果图如下:



用户管理

用户管理功能只为登录凭证,正常此功能应该涉及员工信息、部门信息、职务信息、级别信息等。对用户登录凭证有三种方式限定,账户是否激活,登录账户是否过期,密码是否过期。如果用户输入错误密码次数过多,系统会锁定该账户,禁止登录系统。


一个用户可以分配多个角色,只用用户选定某一角色时方可登录系统。



数据模型设计【mysql版本】


要实现效果图,后台需要表12张,员工信息表、员工职务表、组织结构表、用户信息表、角色信息表、权限信息表、渠道信息表、Appkey秘钥表、用户与角色关联表、角色与权限管理表、权限关联表、渠道与角色关联表。


代码实现

所用语言为JAVA,本文中使用到的技术有Spring Security、Ibatis、JQuery、HTML5。本文中设计代码为关键代码,并非全部实现。需要完整代码,可以联系作者。


权限控制框架目前比较流行的有两种Spring Security以及Shiro。本文使用Spring Security。


Xml配置












UserDetailsService类实现
@Resource(name = "securityDaoHibernater")
private ISecurityDao securityDao;
@Resource(name = "roleDaoHibernater")
private IRoleDao roleDao;
@Override
public UserDetails loadUserByUsername(String username,Authentication authentication) throws UsernameNotFoundException {
return loadUser(username,authentication,"");
}
@Override
public void recordPwdErrorTimes(Object username, Authentication authentication) throws UsernameNotFoundException {
Account _account = getAccountForCode((String)username);
Integer num = _account.getErrorTimes() == null ? 0 : _account.getErrorTimes();
_account.setErrorTimes(++num);
if (calculateIsLocked(_account.getErrorTimes())){
_account.setIslocked(ConstantPool.SYS_ACCOUNT_INFO_ISLOCKED_VALID);
}
_account.setUpdateDate(new Date());
this.securityDao.update(_account);
}
@Override
public void clearPwdErrorTimes(Object username) throws UsernameNotFoundException {
Account _account = getAccountForCode((String)username);
Integer num = _account.getErrorTimes() == null ? 0 : _account.getErrorTimes();
if (num > 0){
_account.setErrorTimes(0);
_account.setUpdateDate(new Date());
this.securityDao.update(_account);
}
}
public UserDetails loadUser(String code, Authentication authentication, String ip) {
Account account = new Account(code);
account = loadUser(account, authentication);
return account;
}
public Account loadUser(Account account, Authentication authentication) {
Account _account = getAccountForCode(account.getCode());
if (_account == null || StringUtils.isEmpty(_account.getCode())) {
throw new UsernameNotFoundException("UsernameNotFound");
} else {
account.initAccount(_account);
account.setRoles(new HashSet());
}
if (_account.getRoles() != null && _account.getRoles().size() > 0) {
Role role;
for (Iterator iterator = _account.getRoles().iterator(); iterator.hasNext(); ) {
role = (Role) iterator.next();
List list = new ArrayList(authentication.getAuthorities());
String roleCode = ((SimpleGrantedAuthority)list.get(0)).getAuthority();
if (StringUtils.equals(role.getCode(),roleCode))
account.getRoles().add(new Role(role));
}
}
return account;
}
public Account getAccountForCode(String code) {
return securityDao.getAccountForCode(code);
}/**
* 判断密码错误次数是否已经超限
* @param errorTimes 密码错误次数
* @return boolean true:超限 false:未超限
*/
private boolean calculateIsLocked(Integer errorTimes){
boolean isLocked = false;
//错误次数可以扩展为表数据维护,数据存放与servletContext中
if (errorTimes >= 5){
isLocked = true;
}
return isLocked;
}
UserDetails类实现
@Override
public boolean isAccountNonExpired() {
return new Date().before(getCodeOverdueDate());
}
@Override
public boolean isAccountNonLocked() {
return ConstantPool.SYS_ACCOUNT_INFO_ISLOCKED_INVALID.equals(getIslocked());
}
@Override
public boolean isCredentialsNonExpired() {
return new Date().before(getPwdOverdueDate());
}
@Override
public boolean isEnabled() {
return ConstantPool.SYS_ACCOUNT_INFO_ISENABLED_VALID.equals(getIsEnabled());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(getRoles() == null || getRoles().size() == 0) {
return null;
}
else {
Set sgset = getRoles();
List sglist = new ArrayList(sgset);
return sglist;
}
}
@Override
public String getUsername() {
return this.getEmpBaseInfo() == null ? "" : this.getEmpBaseInfo().getName();
}
UserLoginTraceService类实现
@Resource(name = "loginInfoDaoHibernate")
private ILoginInfoDao loginInfoDao;
@Override
public void register(UserDetails user, String ip,Object navigatorInfo) {
if (user != null && user instanceof Account) {
Account _account = (Account) user;
NavigatorInfo _navigatorInfo = (NavigatorInfo)navigatorInfo;
LoginInfo li = new LoginInfo();
li.setId(JsupGUID.nextId(ConstantPool.GUID_LENGTH_ID.intValue()));
li.setAccountId(_account.getId());
li.setAccountCode(_account.getCode());
li.setIp(ip);
li.setEquipmentType(_navigatorInfo.getEquipmentType());
li.setNavName(_navigatorInfo.getName());
li.setNavVersion(_navigatorInfo.getVersion());
li.setNavPlatform(_navigatorInfo.getPlatform());
li.setNavShell(_navigatorInfo.getShell());
li.setLoginDate(new Date(System.currentTimeMillis()));
li.setStatus(ConstantPool.SYS_LOGIN_INFO_STATUS_LOGIN);
li.setUpdateDate(new Date(System.currentTimeMillis()));
li.setRoleCode(_account.getRoleCode());
li.setRoleName(_account.getRoleName());
this.loginInfoDao.saveOrUpdate(li);
}
}
@Override
public boolean isRegister(UserDetails user, String ip) throws UserRegisteredException {
Boolean result = false;
if (user != null && user instanceof Account){
Account _account = (Account)user;
LoginInfo li = (LoginInfo)this.loginInfoDao.get(this.loginInfoDao.getMessage("getLoginInfoByIp"), new Object[]{_account.getCode(), _account.getRoleCode(),ip});
if(li != null)
if (ConstantPool.SYS_LOGIN_INFO_STATUS_LOGIN.equals(li.getStatus()))
result = true;
}
return result;
}
@Override
public void cancel(Object objct) {
if(objct instanceof LoginRequest){
LoginRequest request = (LoginRequest)objct;
LoginInfo li = (LoginInfo)this.loginInfoDao.get(this.loginInfoDao.getMessage("getLoginInfoByIp"),
new Object[]{request.getUsername(),request.getRole(),request.getRequestContext().getIp()});
li.setLogoutDate(new Date());
li.setUpdateDate(new Date());
li.setStatus(ConstantPool.SYS_LOGIN_INFO_STATUS_LOGOUT);
this.loginInfoDao.update(li);
}
} 登录注销核心方法代码
public JsupResponse loadUser(LoginRequest request) {
LoginResponse response = new LoginResponse() ;
try {
UsernamePasswordAuthenticationToken authRequest;
if(!StringUtils.isEmpty(request.getRole())) {
SimpleGrantedAuthority sg = new SimpleGrantedAuthority(request.getRole());
List ga = new ArrayList();
ga.add(sg);
authRequest = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword(), ga,request.getNavigatorInfo());
} else {
authRequest = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword(),request.getNavigatorInfo());
}
authRequest.setMac(request.getClientMac());
authRequest.setIp(request.getClientIp());
if(StringUtils.isEmpty(authRequest.getIp())) {
authRequest.setIp(request.getRequestContext().getIp());
request.setClientIp(authRequest.getIp());
}
if(StringUtils.isEmpty(authRequest.getMac())) {
authRequest.setMac(request.getRequestContext().getMac());
request.setClientMac(authRequest.getMac());
}
authRequest = (UsernamePasswordAuthenticationToken)this.authenticationProvider.authenticate(authRequest);
response.setAccount((UserDetails)authRequest.getPrincipal());
DefaultSession ss = new DefaultSession();
if(response.getAccount() != null) {
ss.setAttribute(Constants.SESSION_CURRENT_ACCOUNT, authRequest);
request.getRequestContext().addSession(request.getUsername() + Constants.PERMISSION_SEPARATOR + request.getRole(), ss);
response.setSessionId(request.getRequestContext().getSessionId());
//登录系统毫秒时间
response.setLoginDateTime(Long.valueOf(new Date().getTime()));
response.setIp(authRequest.getIp());
response.setBmsg("登录成功");
}
return response;
} catch (LockedException le) {
//用户锁定
return this.errorResponse(request, NumberCode.USER_LOCKED,"USER_LOCKED", new Object[]{request.getUsername()});
} catch (DisabledException de) {
//用户不可用
return this.errorResponse(request, NumberCode.USER_NOT_ENABLED,"USER_NOT_ENABLED", new Object[]{request.getUsername()});
} catch (AccountExpiredException ae) {
//用户过期
return this.errorResponse(request, NumberCode.USER_NON_EXPIRED,"USER_NON_EXPIRED", new Object[]{request.getUsername()});
} catch (CredentialsExpiredException ce) {
//用户密码凭证过期
return this.errorResponse(request, NumberCode.USER_CREDENTIALS_NON_EXPIRED,"USER_CREDENTIALS_NON_EXPIRED", new Object[]{request.getUsername()});
} catch (BadCredentialsException be) {
//密码错误
return this.errorResponse(request,NumberCode.USER_BAD_CREDENTIALS, "USER_BAD_CREDENTIALS", new Object[]{request.getUsername()});
} catch (UsernameNotFoundException unf){
//用户名不存在
return this.errorResponse(request, NumberCode.USER_NOT_FOUND,"USER_NOT_FOUND",new Object[]{request.getUsername()});
} catch (UserRegisteredException ure){
//用户已登录
return this.errorResponse(request, NumberCode.USER_ALREADY_LOGIN,"USER_ALREADY_LOGIN",new Object[]{request.getUsername(),request.getClientIp()});
}
}
public JsupResponse logout(LoginRequest request) {
LoginResponse response = new LoginResponse();
//删除会话
if (request.getRequestContext().getSession() != null){
request.getRequestContext().removeSession();
}
//更新状态
this.securityTraceService.cancel(request);
response.setBmsg("签退成功");
return response;
}

欢迎大家提出建议。让我们一起享受代码的乐趣。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台