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

前言

        随着移动互联网的发展,系统使用者从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;
}

 

 

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

转载于:https://my.oschina.net/jsup/blog/822372

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值