前言
随着移动互联网的发展,系统使用者从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配置
<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider" > <beans:property name="userDetailsService" ref="securityService"/> <beans:property name="userLoginTraceService" ref="securityTraceService"/> <beans:property name="passwordEncoder" ref="passwordEncoder"/> <!--<beans:property name="userCache" ref="userCache"/>--> </beans:bean> <!--载入用户信息--> <beans:bean id="securityService" class="com.jsup.service.bo.security.service.impl.SecurityServiceImpl"/> <!--记录登录日志--> <beans:bean id="securityTraceService" class="com.jsup.service.bo.security.service.impl.SecurityTraceServiceImpl"/> <!--密码验证--> <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>
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<GrantedAuthority> sgset = getRoles(); List<GrantedAuthority> sglist = new ArrayList<GrantedAuthority>(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<GrantedAuthority> ga = new ArrayList<GrantedAuthority>(); 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; }
欢迎大家提出建议。让我们一起享受代码的乐趣。