基於角色的訪問控制 RBAC(role based access control),基於角色的訪問控制。 好比: 系統角色包括 :部門經理、總經理。(角色針對用戶來劃分) 系統代碼中實現: //若是該user是部門經理則能夠訪問if中的代碼 if(user.hasRole('部門經理')){ //系統資源內容 //用戶報表查看 } 問題: 角色針對人劃分的,人做爲用戶在系統中屬於活動內容,若是該 角色能夠訪問的資源出現變動,須要修改你的代碼了,好比:須要變動爲部門經理和總經理均可以進行用戶報表查看,代碼改成: if(user.hasRole('部門經理') || user.hasRole('總經理') ){ //系統資源內容 //用戶報表查看 } 基於角色的訪問控制是不利於系統維護(可擴展性不強)。 基於資源的訪問控制 RBAC(Resource based access control),基於資源的訪問控制。 資源在系統中是不變的,好比資源有:類中的方法,頁面中的按鈕。 對資源的訪問須要具備permission權限,代碼能夠寫爲: if(user.hasPermission ('用戶報表查看(權限標識符)')){ //系統資源內容 //用戶報表查看 } 上邊的方法就能夠解決用戶角色變動不用修改上邊權限控制的代碼。 若是須要變動權限只須要在分配權限模塊去操做,給部門經理或總經理增或刪除權限。 建議使用基於資源的訪問控制實現權限管理。
粗粒度權限管理,對資源類型的權限管理。細粒度權限管理,對資源實例的權限管理。
粗粒度權限管理,對資源類型的權限管理。資源類型好比:菜單、url鏈接、用戶添加頁面、用戶信息、類方法、頁面中按鈕。。
粗粒度權限管理好比:超級管理員能夠訪問戶添加頁面、用戶信息等所有頁面。
部門管理員能夠訪問用戶信息頁面包括 頁面中全部按鈕。
細粒度權限管理,對資源實例的權限管理。資源實例就資源類型的具體化,好比:用戶id爲001的修改鏈接,1110班的用戶信息、行政部的員工。
細粒度權限管理就是數據級別的權限管理。
細粒度權限管理好比:部門經理只能夠訪問本部門的員工信息,用戶只能夠看到本身的菜單,大區經理只能查看本轄區的銷售訂單。。
粗粒度和細粒度例子:(spring security是類型級別)
系統有一個用戶列表查詢頁面,對用戶列表查詢分權限,若是粗顆粒管理,張三和李四都有用戶列表查詢的權限,張三和李四均可以訪問用戶列表查詢。
進一步進行細顆粒管理,張三(行政部)和李四(開發部)只能夠查詢本身本部門的用戶信息。張三隻能查看行政部 的用戶信息,李四隻能查看開發部門的用戶信息。細粒度權限管理就是數據級別的權限管理。
如何實現粗粒度權限管理?
粗粒度權限管理比較容易將權限管理的代碼抽取出來在系統架構級別統一處理。好比:經過springmvc的攔截器實現受權。
如何實現細粒度權限管理? 複雜
對細粒度權限管理在數據級別是沒有共性可言,針對細粒度權限管理就是系統業務邏輯的一部分,若是在業務層去處理相對比較簡單,若是將細粒度權限管理統一在系統架構級別去抽取,比較困難,即便抽取的功能可能也存在擴展不強。
建議細粒度權限管理在業務層去控制。
好比:部門經理只查詢本部門員工信息,在service接口提供一個部門id的參數,controller中根據當前用戶的信息獲得該 用戶屬於哪一個部門,調用service時將部門id傳入service,實現該用戶只查詢本部門的員工。
對於粗粒度權限管理,建議使用優秀權限管理框架來實現,節省開發成功,提升開發效率。
shiro就是一個優秀權限管理框架。
不用權限框架,是用基於url攔截的方式實如今實際開發中比較經常使用的一種方式。
對於web系統,經過filter過慮器實現url攔截,也能夠springmvc框架的攔截器實現基於url的攔截。
基於url的方式是不須要shiro,對於細粒度的放在業務層去實現,粗粒度的用框架或者url攔截方式。java
基於url權限管理流程:有認證過濾器和受權過濾器。「我的中心」就是認證經過可是不須要受權的url。都是以url進行操做的。
percode是權限標識符,parentids是權限路徑,sortstring是排序字段,mysql
mysql5.1數據庫中建立表:用戶表、角色表、權限表(實質上是權限和資源的結合 )、用戶角色表、角色權限表。jquery
完成權限管理的數據模型建立。web
jdk1.7.0_72redis
eclipse 3.7 indigospring
技術架構:springmvc+mybatis+jquery-easyuisql
新建一個動態web項目。數據庫
建立config文件夾緩存
系統 登錄至關 於用戶身份認證,用戶成功,要在session中記錄用戶的身份信息.
操做流程:
用戶進行登錄頁面
輸入用戶名和密碼進行登錄
進行用戶名和密碼校驗:service(進行用戶名和密碼校驗)
若是校驗經過,在session記錄用戶身份信息:controler裏面記錄session,只有controller能夠訪問session.
建立專門類用於記錄用戶身份信息。
mapper接口: 根據用戶帳號查詢用戶(sys_user)信息(不用寫,使用逆向工程生成的mapper)
使用逆向工程生成如下表的基礎代碼:
Service:session
public class SysServiceImpl implements SysService { @Autowired private SysUserMapper sysUserMapper; @Autowired private SysPermissionMapperCustom sysPermissionMapperCustom; @Override public ActiveUser authenticat(String userCode, String password) throws Exception { /** 認證過程: 根據用戶身份(帳號)查詢數據庫,若是查詢不到用戶不存在 對輸入的密碼 和數據庫密碼 進行比對,若是一致,認證經過 */ //根據用戶帳號查詢數據庫 SysUser sysUser = this.findSysUserByUserCode(userCode); if(sysUser == null){ //拋出異常 throw new CustomException("用戶帳號不存在"); } //數據庫密碼 (md5密碼 ) String password_db = sysUser.getPassword(); //對輸入的密碼 和數據庫密碼 進行比對,若是一致,認證經過 //對頁面輸入的密碼 進行md5加密 String password_input_md5 = new MD5().getMD5ofStr(password); if(!password_input_md5.equalsIgnoreCase(password_db)){ //拋出異常 throw new CustomException("用戶名或密碼 錯誤"); } //獲得用戶id String userid = sysUser.getId(); //根據用戶id查詢菜單 List<SysPermission> menus =this.findMenuListByUserId(userid); //根據用戶id查詢權限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //認證經過,返回用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(userCode); activeUser.setUsername(sysUser.getUsername());//用戶名稱 //放入權限範圍的菜單和url activeUser.setMenus(menus); activeUser.setPermissions(permissions); return activeUser; }
Controller:
@Autowired private SysService sysService; //用戶登錄提交方法 /** * @param session * @param randomcode 輸入的驗證碼 * @param usercode 用戶帳號 * @param password 用戶密碼 */ @RequestMapping("/login") public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{ //校驗驗證碼,防止惡性攻擊 //從session獲取正確驗證碼 String validateCode = (String) session.getAttribute("validateCode"); //輸入的驗證和session中的驗證進行對比 if(!randomcode.equals(validateCode)){ //拋出異常 throw new CustomException("驗證碼輸入錯誤"); } //調用service校驗用戶帳號和密碼的正確性 ActiveUser activeUser = sysService.authenticat(usercode, password); //若是service校驗經過,將用戶身份記錄到session session.setAttribute("activeUser", activeUser); //重定向到商品查詢頁面 return "redirect:/first.action"; }
配置能夠匿名訪問的url,直接放行。
資源文件讀取類:
package cn.itcast.ssm.util; import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; /** * 資源文件讀取工具類 */ public class ResourcesUtil implements Serializable { private static final long serialVersionUID = -7657898714983901418L; /** * 系統語言環境,默認爲中文zh */ public static final String LANGUAGE = "zh"; /** * 系統國家環境,默認爲中國CN */ public static final String COUNTRY = "CN"; private static Locale getLocale() { Locale locale = new Locale(LANGUAGE, COUNTRY); return locale; } /** * 根據語言、國家、資源文件名和key名字獲取資源文件值 * @param language 語言 * @param country國家 * @param fileName資源文件名 * @param key名字 * @return 值 */ private static String getProperties(String fileName, String key) { String retValue = ""; try { Locale locale = getLocale(); ResourceBundle rb = ResourceBundle.getBundle(fileName, locale); retValue = (String) rb.getObject(key); } catch (Exception e) { e.printStackTrace(); } return retValue; } /** * 經過key從資源文件讀取內容 * @param fileName資源文件名 * @param key索引 * @return 索引對應的內容 */ public static String getValue(String fileName, String key) { String value = getProperties(fileName,key); return value; } public static List<String> gekeyList(String baseName) { Locale locale = getLocale(); ResourceBundle rb = ResourceBundle.getBundle(baseName, locale); List<String> reslist = new ArrayList<String>(); Set<String> keyset = rb.keySet(); for (Iterator<String> it = keyset.iterator(); it.hasNext();) { String lkey = (String)it.next(); reslist.add(lkey); } return reslist; } /** * 經過key從資源文件讀取內容,並格式化 * @param fileName資源文件名 * @param key索引 * @param objs 格式化參數 * @return 格式化後的內容 */ public static String getValue(String fileName, String key, Object[] objs) { String pattern = getValue(fileName, key); String value = MessageFormat.format(pattern, objs); return value; } public static void main(String[] args) { System.out.println(getValue("resources.messages", "101",new Object[]{100,200})); //根據操做系統環境獲取語言環境 /*Locale locale = Locale.getDefault(); System.out.println(locale.getCountry());//輸出國家代碼 System.out.println(locale.getLanguage());//輸出語言代碼s //加載國際化資源(classpath下resources目錄下的messages.properties,若是是中文環境會優先找messages_zh_CN.properties) ResourceBundle rb = ResourceBundle.getBundle("resources.messages", locale); String retValue = rb.getString("101");//101是messages.properties文件中的key System.out.println(retValue); //信息格式化,若是資源中有{}的參數則須要使用MessageFormat格式化,Object[]爲傳遞的參數,數量根據資源文件中的{}個數決定 String value = MessageFormat.format(retValue, new Object[]{100,200}); System.out.println(value);*/ } }
編寫認證攔截器 package cn.itcast.ssm.controller.interceptor; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import cn.itcast.ssm.po.ActiveUser; import cn.itcast.ssm.util.ResourcesUtil; /** * * <p>Title: HandlerInterceptor1</p> * <p>Description: 用戶身份認證攔截器</p> * <p>Company: www.itcast.com</p> * @author 傳智.燕青 * @date 2015-3-22下午4:11:44 * @version 1.0 */ public class LoginInterceptor implements HandlerInterceptor { //在執行handler以前來執行的 //用於用戶認證校驗、用戶權限校驗 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //獲得請求的url String url = request.getRequestURI(); //判斷是不是公開 地址 //實際開發中須要公開 地址配置在配置文件中 //從配置中取逆名訪問url List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL"); //遍歷公開 地址,若是是公開 地址則放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //若是是公開 地址則放行,不然跳轉到登錄頁面 return true; } } //判斷用戶身份在session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //若是用戶身份在session中存在放行 if(activeUser!=null){ return true; } //執行到這裏攔截,跳轉到登錄頁面,用戶進行身份認證 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //若是返回false表示攔截不繼續執行handler,若是返回true表示放行 return false; } //在執行handler返回modelAndView以前來執行 //若是須要向頁面提供一些公用 的數據或配置一些視圖信息,使用此方法實現 從modelAndView入手 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor1...postHandle"); } //執行handler以後執行此方法 //做系統 統一異常處理,進行方法執行性能監控,在preHandle中設置一個時間點,在afterCompletion設置一個時間,兩個時間點的差就是執行時長 //實現 系統 統一日誌記錄 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("HandlerInterceptor1...afterCompletion"); } }
配置攔截器
在springmvc.xml中配置攔截器
commonURL.properties
在此配置文件配置公用訪問地址,公用訪問地址只要經過用戶認證,不須要對公用訪問地址分配權限便可訪問。好比首頁和退出,只須要登錄,每一個用戶都同樣。
用戶權限相關的東西能夠放在緩存或者session或者redis中。
獲取用戶權限範圍的菜單
思路:
在用戶認證時,認證經過,根據用戶id從數據庫獲取用戶權限範圍的菜單,將菜單的集合存儲在session中。
mapper接口:根據用戶id查詢用戶權限的菜單