
具體可參見(jiàn)百度:http://baike.baidu.com/view/2108713.htm
名詞備注:
數據級權限:百科內的權限管理一文解釋的比較不錯,但其中的“數據級權限”有的人看來(lái)會(huì )覺(jué)得有點(diǎn)摸不著(zhù)頭腦。數據級權限,即表示權限與特定數據有聯(lián)系的權限,比方說(shuō),某用戶(hù)只能創(chuàng )建100個(gè)用戶(hù)。這個(gè)100,就是數據級權限的一個(gè)指標。
也許很多程序員會(huì )在權限管理中遇到這樣的一個(gè)問(wèn)題。
大部分項目都需要權限管理系統,但不同的項目背景中,角色的種類(lèi)和對應的權限靈活多變。往往需要在維護和調研時(shí)花費大量的功夫去分析,而最后由于不同客戶(hù)方不同層面的領(lǐng)導的意見(jiàn)不一或者不同的決策等問(wèn)題,造成多次的翻工(也許開(kāi)始你定好了適合他們的權限機制,但后來(lái)又有些不可抗拒因素導致你又要修改項目)。
這樣的問(wèn)題是一種無(wú)用功,而且是十分讓人煩惱的。
如何去解決怎樣的問(wèn)題?

保存基本的用戶(hù)名,密碼,角色表id和用戶(hù)狀態(tài)。
管理員可以修改用戶(hù)的角色。
PS:系統最基本的狀態(tài)至少需要保留一個(gè)默認管理員賬號。
用來(lái)判定(vote)功能及數據級權限管理的依據。
項目創(chuàng )建者內置的權限集合,不給與管理的權限。否則將可能造成項目功能的缺陷。
2.2.3、角色role
決定用戶(hù)具體包含權限列表。
role_privilege連接role與privilege兩個(gè)表用來(lái)表示關(guān)系表連接構造many-to-many關(guān)系。
同樣的,系統默認狀態(tài)也必須保留至少一種角色為系統自帶的管理,這個(gè)角色具備系統中全部的權限。也就是說(shuō),該角色不受role_ privilege表所限制,會(huì )直接讀取privilege中的所有權限集。
2.2.4、權限分類(lèi)privilege_category
為了更加完善的展現權限分配模塊,可以構造一個(gè)權限分類(lèi)。
具體SpringSecurity3的配置這里就不詳細說(shuō)明了,可參考網(wǎng)上其他資料。因為本文主要講述的是如何實(shí)現RBAC權限管理模塊。
注:自定義的權限的命名必須以ROLE_ 開(kāi)頭,例如ROLE_USER_CREATE等。
使用SpringSecurity3的時(shí)候,網(wǎng)上很多的資料都能讓你的模塊跑起來(lái)。但隨之而來(lái)的是一些誤區。
比方說(shuō),會(huì )把權限和角色兩者混淆。比較經(jīng)典的例子如下:
Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();GrantedAuthorityImpl roleAdmin=new GrantedAuthorityImpl("ROLE_ADMIN");GrantedAuthorityImpl roleUser=new GrantedAuthorityImpl("ROLE_USER");auths.add(roleAdmin);auths.add(roleUser);
然后訪(fǎng)問(wèn)權限的配置如下:
<intercept-url pattern="/**" access="ROLE_USER" />
雖然也許看上去沒(méi)什么問(wèn)題,但其實(shí)存在一定的隱患。因為能訪(fǎng)問(wèn)頁(yè)面的不是因為他是user,而是因為他有“訪(fǎng)問(wèn)”的權限。
如果后來(lái)增加了一個(gè)guest的角色,而他能訪(fǎng)問(wèn)系統,但不能含有user的權限。因為user的權限可能有附帶很多界面上的功能,但不附上ROLE_USER的話(huà)guest又不能訪(fǎng)問(wèn)系統,所以你就不得不修改配置文件中的access。
造成這個(gè)的問(wèn)題的最終原因就是角色和權限混淆了。
要想最大限度把權限分配變得靈活,角色提供可摘取權限的功能是必不可少的。而針對不同的項目背景,所有的角色和權限也許會(huì )出現各種的變化。但其功能還是離不開(kāi)分配角色和分配權限。
而通過(guò)role_privilege表,我們可以在用戶(hù)登錄系統的時(shí)候,把該用戶(hù)角色的權限通過(guò)SpringSecurity幫助我們放到用戶(hù)的權限組內。從而我們可以利用SpringSecurity提供的各種標簽,標注訪(fǎng)問(wèn)控制等實(shí)現權限功能管理和現實(shí)。
例子:
<sec:authorize ifAnyGranted="ROLE_CREATE,ROLE_UPDATE,ROLE_READ,ROLE_DELETE"> <a>用戶(hù)管理</a> </sec:authorize>
即用戶(hù)擁有OLE_CREATE,ROLE_UPDATE,ROLE_READ,ROLE_DELETE任何一個(gè)權限的時(shí)候才能查看到用戶(hù)管理按鈕。
點(diǎn)進(jìn)去之后,我們可以再細分對應的權限操作,沒(méi)有的權限則該功能模塊會(huì )不出現。
有的時(shí)候,權限還必須有“包含”關(guān)系,即若你具備了某權限,則另外的權限你也必定會(huì )具備。
比方說(shuō),有個(gè)角色他有刪除用戶(hù)的權限,但他沒(méi)有讀取用戶(hù)的權限。這樣覺(jué)得有沒(méi)有問(wèn)題呢?
若用戶(hù)沒(méi)有讀取用戶(hù)的權限,連列表都不出來(lái),那他如何實(shí)現刪除?這樣看上去雖然不是系統上的問(wèn)題。但一個(gè)完善的系統,必須去考慮這樣的情況發(fā)生。
import java.util.ArrayList;import java.util.Collection;import java.util.List;import java.util.Set;import org.apache.commons.collections.CollectionUtils;import org.apache.commons.lang.StringUtils;import org.springframework.dao.DataAccessException;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.GrantedAuthorityImpl;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import cn.com.timekey.drugmonitor.business.PrivilegeBus;import cn.com.timekey.drugmonitor.business.UserBus;import cn.com.timekey.drugmonitor.log.Log;import cn.com.timekey.drugmonitor.log.LogFactory;import cn.com.timekey.drugmonitor.po.Privilege;import cn.com.timekey.drugmonitor.po.Role;import cn.com.timekey.drugmonitor.po.RolePrivilege;import cn.com.timekey.drugmonitor.po.Users;/** * @author Kenny */public class MyUserDetailsService implements UserDetailsService { private static final Log LOGGER = LogFactory .getLog(MyUserDetailsService.class); private static final String SYSTEM_ROLE_ID = "1";// 系統默認管理員的id private UserBus userBus; private PrivilegeBus privilegeBus; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { if (StringUtils.isBlank(username)) { throw new UsernameNotFoundException("no such user.", username); } Users user = userBus.findByName(username); if (user == null) { LOGGER.debug("no such user by " + username); throw new UsernameNotFoundException("no such user.", username); } else if (user.getRole() == null) { LOGGER.debug("no such role by " + username); throw new UsernameNotFoundException("no such user.", username); } String adminName = user.getUserName(); String password = user.getUserPassword(); Role role = user.getRole(); @SuppressWarnings("unchecked") Collection<Privilege> privileges = CollectionUtils.EMPTY_COLLECTION; // 判斷是否為系統默認管理員,若是,則直接獲取privilege表中全部權限。 if (StringUtils.equals(role.getRoleId(), SYSTEM_ROLE_ID)) { privileges = privilegeBus.findAll(); } Set<RolePrivilege> rolePrivileges = role.getRolePrivileges(); @SuppressWarnings("unchecked") Collection<GrantedAuthority> authorities = CollectionUtils.EMPTY_COLLECTION; if (privileges.isEmpty() && rolePrivileges != null && !rolePrivileges.isEmpty()) { privileges = new ArrayList<Privilege>(rolePrivileges.size()); for (RolePrivilege rolePrivilege : rolePrivileges) { privileges.add(rolePrivilege.getPrivilege()); } } if (privileges.isEmpty()) { LOGGER.warn("user has not any rolePrivileges."); throw new UsernameNotFoundException("Privilege fail! ", username); } // 構造權限組 authorities = generateAuthorities(privileges); boolean isEnable = user.getIsActive();// 如果賬號有狀態(tài)的話(huà),可根據查詢(xún)結果配置該值。 return new org.springframework.security.core.userdetails.User( adminName, password, isEnable, true, true, true, authorities); } /** * 構造權限組 * * @param rolePrivileges * @return */ private Collection<GrantedAuthority> generateAuthorities( Collection<Privilege> privileges) { List<GrantedAuthority> auth = new ArrayList<GrantedAuthority>( privileges.size()); for (Privilege rolePrivilege : privileges) { GrantedAuthority authority = new GrantedAuthorityImpl( rolePrivilege.getPrivilegeName()); auth.add(authority); } return auth; } public void setUserBus(UserBus userBus) { this.userBus = userBus; } public void setPrivilegeBus(PrivilegeBus privilegeBus) { this.privilegeBus = privilegeBus; }}
出來(lái)頁(yè)面上使用TAG來(lái)實(shí)現基本的權限功能隱藏與顯示外。還必須注意系統內部的權限判斷。
TAG幫我們隱藏了功能的URL,但該URL還是存在的,只要對方知道URL就能直接發(fā)請求過(guò)來(lái)了,從而繞過(guò)了權限管理。
我們可以使用攔截器進(jìn)一步處理請求權限的問(wèn)題。具體可以在<http auto-config>代碼塊中配置,如:
<intercept-url pattern="/createUser.do" access="ROLE_USER_CREATE" />
<intercept-url pattern="/listUser.do" access="ROLE_USER_READ" /> <intercept-url pattern="/**" access="ROLE_LOGIN" />
解釋?zhuān)?/p>
訪(fǎng)問(wèn)/createUser.do資源必須有ROLE_USER_CREATE權限。
訪(fǎng)問(wèn)/listUser.do資源必須有ROLE_USER_READ。
訪(fǎng)問(wèn)任何資源都必須有ROLE_LOGIN才行。(也可以用IS_AUTHENTICATED_REMEMBERED)
有的時(shí)候,程序員在編碼中會(huì )出現疏忽,導致引用錯方法?;蛘咴诰幋a時(shí),沒(méi)有考慮到權限的問(wèn)題造成一些跨權限的漏洞。
比方說(shuō),某個(gè)功能因為疏忽,沒(méi)在攔截器上配置權限攔截,或者功能定義了兩個(gè)URL入口,而只有其中一個(gè)在攔截器上配置了(一個(gè)也許是系統舊的遺留入口)。
這樣的情況下就會(huì )帶來(lái)權限漏洞,有不良目的人就可以使用這些漏洞來(lái)攻擊系統,但最重要的還是造成客戶(hù)損失。
要更進(jìn)一步的加強“保險”,我們還可以使用標注在代碼里面聲明擁有某種權限才能調用特定的方法(也可以使用AOP聲明的方式,但個(gè)人更加喜歡標注的形式,但標注的話(huà)相對于會(huì )硬編碼些)。
使用標注時(shí),記得要在添加上下面代碼才生效喔!
<global-method-security secured-annotations="enabled"> </global-method-security>
例子如:
import org.springframework.security.access.annotation.Secured;public interface AccountBusiness { @Secured("ROLE_USER_CREATE") public void save(User user); @Secured("ROLE_USER_DELETE") public void delete(String id);}
我們再不用去考慮系統中不同角色的有什么權限的問(wèn)題了,因為權限分配十分靈活化。程序員不必糾結不同項目中,什么角色應該具備哪些權限而煩惱了,但我們必須配置好一套完善的權限列表來(lái)滿(mǎn)足用戶(hù)的分配需求。雖然不完美,但減少了很多的煩惱。
角色與權限分配的功能由客戶(hù)或者業(yè)務(wù)人員自己來(lái)決定。我們只要提供好足夠滿(mǎn)足對方需求的權限范圍就可以了。權限缺少的時(shí)候,我們可以增加,但這些工作不至于是無(wú)用功。
1.用戶(hù)登錄時(shí),必須重新從數據庫里面拿角色對應的權限集,資源消耗是否有點(diǎn)多?
就這個(gè)問(wèn)題而言,我是覺(jué)得沒(méi)必要計較這些資源消耗的,因為權限集再怎么多也不會(huì )超過(guò)50條吧。
而權限管理系統,一般并發(fā)量也不會(huì )大的了。如果真的糾結這樣的消耗,也可以放用static map用來(lái)實(shí)現角色與權限集的獲取,但記得用上觀(guān)察者模式。因為權限集是可以被修改的,不用觀(guān)察者的話(huà)就會(huì )出現得到過(guò)期的權限集了。
2.雖然考慮到數據級的權限管理問(wèn)題,但目前還是沒(méi)有提供這樣的案例。
3.Group用戶(hù)組還不在此架構范圍內。
聯(lián)系客服