前言
本文主要講解的知識(shí)點(diǎn)有以下:
- 權(quán)限管理的基礎(chǔ)知識(shí)
- 模型
- 粗粒度和細(xì)粒度的概念
- 回顧URL攔截的實(shí)現(xiàn)
- Shiro的介紹與簡(jiǎn)單入門
一、Shiro基礎(chǔ)知識(shí)
在學(xué)習(xí)Shiro這個(gè)框架之前,首先我們要先了解Shiro需要的基礎(chǔ)知識(shí):權(quán)限管理
1.1什么是權(quán)限管理?
只要有用戶參與的系統(tǒng)一般都要有權(quán)限管理,權(quán)限管理實(shí)現(xiàn)對(duì)用戶訪問(wèn)系統(tǒng)的控制,按照安全規(guī)則或者安全策略控制用戶可以訪問(wèn)而且只能訪問(wèn)自己被授權(quán)的資源。
對(duì)權(quán)限的管理又分為兩大類別:
- 用戶認(rèn)證
- 用戶授權(quán)
1.1.1用戶認(rèn)證
用戶認(rèn)證,用戶去訪問(wèn)系統(tǒng),系統(tǒng)要驗(yàn)證用戶身份的合法性
最常用的用戶身份驗(yàn)證的方法:1、用戶名密碼方式、2、指紋打卡機(jī)、3、基于證書驗(yàn)證方法。。系統(tǒng)驗(yàn)證用戶身份合法,用戶方可訪問(wèn)系統(tǒng)的資源。
舉個(gè)例子:
- 當(dāng)我們輸入了自己的淘寶的賬戶和密碼,才能打開購(gòu)物車
用戶認(rèn)證的流程:
- 判斷該資源能否不認(rèn)證就能訪問(wèn)【登陸頁(yè)面、首頁(yè)】
- 如果該資源需要認(rèn)證后才能訪問(wèn),那么判斷該訪問(wèn)者是否認(rèn)證了
- 如果還沒(méi)有認(rèn)證,那么需要返回到【登陸頁(yè)面】進(jìn)行認(rèn)證
- 認(rèn)證通過(guò)后才能訪問(wèn)資源
這里寫圖片描述
從用戶認(rèn)證我們可以抽取出這么幾個(gè)概念
- subject主體:理解為用戶,可能是程序,都要去訪問(wèn)系統(tǒng)的資源,系統(tǒng)需要對(duì)subject進(jìn)行身份認(rèn)證
- principal身份信息:通常是唯一的,一個(gè)主體還有多個(gè)身份信息,但是都有一個(gè)主身份信息(primary principal)【我們可以選擇身份證認(rèn)證、學(xué)生證認(rèn)證等等都是我們的身份信息】
- credential憑證信息:可以是密碼 、證書、指紋。
總結(jié):主體在進(jìn)行身份認(rèn)證時(shí)需要提供身份信息和憑證信息。
1.1.2用戶授權(quán)
用戶授權(quán),簡(jiǎn)單理解為訪問(wèn)控制,在用戶認(rèn)證通過(guò)后,系統(tǒng)對(duì)用戶訪問(wèn)資源進(jìn)行控制,用戶具有資源的訪問(wèn)權(quán)限方可訪問(wèn)。
用戶授權(quán)的流程
- 到達(dá)了用戶授權(quán)環(huán)節(jié),當(dāng)然是需要用戶認(rèn)證之后了
- 用戶訪問(wèn)資源,系統(tǒng)判斷該用戶是否有權(quán)限去操作該資源
- 如果該用戶有權(quán)限才能夠訪問(wèn),如果沒(méi)有權(quán)限就不能訪問(wèn)了
這里寫圖片描述
授權(quán)的過(guò)程可以簡(jiǎn)單理解為:主體認(rèn)證之后,系統(tǒng)進(jìn)行訪問(wèn)控制
subject必須具備資源的訪問(wèn)權(quán)限才可訪問(wèn)該資源..
權(quán)限/許可(permission) :針對(duì)資源的權(quán)限或許可,subject具有permission訪問(wèn)資源,如何訪問(wèn)/操作需要定義permission,權(quán)限比如:用戶添加、用戶修改、商品刪除
資源可以分為兩種
- 資源類型:系統(tǒng)的用戶信息就是資源類型,相當(dāng)于java類。
- 資源實(shí)例:系統(tǒng)中id為001的用戶就是資源實(shí)例,相當(dāng)于new的java對(duì)象。
1.2權(quán)限管理模型
一般地,我們可以抽取出這么幾個(gè)模型:
- 主體(賬號(hào)、密碼)
- 資源(資源名稱、訪問(wèn)地址)
- 權(quán)限(權(quán)限名稱、資源id)
- 角色(角色名稱)
- 角色和權(quán)限關(guān)系(角色id、權(quán)限id)
- 主體和角色關(guān)系(主體id、角色id)
這里寫圖片描述
通常企業(yè)開發(fā)中將資源和權(quán)限表合并為一張權(quán)限表,如下:
- 資源(資源名稱、訪問(wèn)地址)
- 權(quán)限(權(quán)限名稱、資源id)
合并為:
- 權(quán)限(權(quán)限名稱、資源名稱、資源訪問(wèn)地址)
這里寫圖片描述
1.3分配權(quán)限
用戶需要分配相應(yīng)的權(quán)限才可訪問(wèn)相應(yīng)的資源。權(quán)限是對(duì)于資源的操作許可。
通常給用戶分配資源權(quán)限需要將權(quán)限信息持久化,比如存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中。把用戶信息、權(quán)限管理、用戶分配的權(quán)限信息寫到數(shù)據(jù)庫(kù)(權(quán)限數(shù)據(jù)模型)
1.3.1基于角色訪問(wèn)控制
RBAC(role based access control),基于角色的訪問(wèn)控制。
//如果該user是部門經(jīng)理則可以訪問(wèn)if中的代碼if(user.hasRole(\’部門經(jīng)理\’)){ //系統(tǒng)資源內(nèi)容 //用戶報(bào)表查看}
角色針對(duì)人劃分的,人作為用戶在系統(tǒng)中屬于活動(dòng)內(nèi)容,如果該 角色可以訪問(wèn)的資源出現(xiàn)變更,需要修改你的代碼了,
if(user.hasRole(\’部門經(jīng)理\’) || user.hasRole(\’總經(jīng)理\’) ){ //系統(tǒng)資源內(nèi)容 //用戶報(bào)表查看}
基于角色的訪問(wèn)控制是不利于系統(tǒng)維護(hù)(可擴(kuò)展性不強(qiáng))。
1.3.2基于資源的訪問(wèn)控制
RBAC(Resource based access control),基于資源的訪問(wèn)控制。
資源在系統(tǒng)中是不變的,比如資源有:類中的方法,頁(yè)面中的按鈕。
對(duì)資源的訪問(wèn)需要具有permission權(quán)限,代碼可以寫為:if(user.hasPermission (\’用戶報(bào)表查看(權(quán)限標(biāo)識(shí)符)\’)){ //系統(tǒng)資源內(nèi)容 //用戶報(bào)表查看}
建議使用基于資源的訪問(wèn)控制實(shí)現(xiàn)權(quán)限管理。
二、 粗粒度和細(xì)粒度權(quán)限
細(xì)粒度權(quán)限管理:對(duì)資源實(shí)例的權(quán)限管理。資源實(shí)例就資源類型的具體化,比如:用戶id為001的修改連接,1110班的用戶信息、行政部的員工。細(xì)粒度權(quán)限管理就是數(shù)據(jù)級(jí)別的權(quán)限管理。
粗粒度權(quán)限管理比如:超級(jí)管理員可以訪問(wèn)戶添加頁(yè)面、用戶信息等全部頁(yè)面。部門管理員可以訪問(wèn)用戶信息頁(yè)面包括 頁(yè)面中所有按鈕。
粗粒度和細(xì)粒度例子:
系統(tǒng)有一個(gè)用戶列表查詢頁(yè)面,對(duì)用戶列表查詢分權(quán)限,如果粗顆粒管理,張三和李四都有用戶列表查詢的權(quán)限,張三和李四都可以訪問(wèn)用戶列表查詢。進(jìn)一步進(jìn)行細(xì)顆粒管理,張三(行政部)和李四(開發(fā)部)只可以查詢自己本部門的用戶信息。張三只能查看行政部 的用戶信息,李四只能查看開發(fā)部門的用戶信息。細(xì)粒度權(quán)限管理就是數(shù)據(jù)級(jí)別的權(quán)限管理。
2.1如何實(shí)現(xiàn)粗粒度權(quán)限管理?
粗粒度權(quán)限管理比較容易將權(quán)限管理的代碼抽取出來(lái)在系統(tǒng)架構(gòu)級(jí)別統(tǒng)一處理。比如:通過(guò)springmvc的攔截器實(shí)現(xiàn)授權(quán)。
對(duì)細(xì)粒度權(quán)限管理在數(shù)據(jù)級(jí)別是沒(méi)有共性可言,針對(duì)細(xì)粒度權(quán)限管理就是系統(tǒng)業(yè)務(wù)邏輯的一部分,在業(yè)務(wù)層去處理相對(duì)比較簡(jiǎn)單
比如:部門經(jīng)理只查詢本部門員工信息,在service接口提供一個(gè)部門id的參數(shù),controller中根據(jù)當(dāng)前用戶的信息得到該 用戶屬于哪個(gè)部門,調(diào)用service時(shí)將部門id傳入service,實(shí)現(xiàn)該用戶只查詢本部門的員工。
2.1.1基于URL攔截
基于url攔截的方式實(shí)現(xiàn)在實(shí)際開發(fā)中比較常用的一種方式。
對(duì)于web系統(tǒng),通過(guò)filter過(guò)慮器實(shí)現(xiàn)url攔截,也可以springmvc的攔截器實(shí)現(xiàn)基于url的攔截。
2.2.2使用權(quán)限管理框架實(shí)現(xiàn)
對(duì)于粗粒度權(quán)限管理,建議使用優(yōu)秀權(quán)限管理框架來(lái)實(shí)現(xiàn),節(jié)省開發(fā)成功,提高開發(fā)效率。
shiro就是一個(gè)優(yōu)秀權(quán)限管理框架。
三、回顧URL攔截
我們?cè)趯W(xué)習(xí)的路途上也是使用過(guò)幾次URL對(duì)權(quán)限進(jìn)行攔截的
當(dāng)時(shí)我們做了權(quán)限的增刪該查的管理系統(tǒng),但是在權(quán)限表中是沒(méi)有把資源添加進(jìn)去,我們使用的是Map集合來(lái)進(jìn)行替代的。
http://blog.csdn.net/hon_3y/article/details/61926175
隨后,我們學(xué)習(xí)了動(dòng)態(tài)代理和注解,我們也做了一個(gè)基于注解的攔截
- 在Controller得到service對(duì)象的時(shí)候,service工廠返回的是一個(gè)動(dòng)態(tài)代理對(duì)象回去
- Controller拿著代理對(duì)象去調(diào)用方法,代理對(duì)象就會(huì)去解析該方法上是否有注解
- 如果有注解,那么就需要我們進(jìn)行判斷該主體是否認(rèn)證了,如果認(rèn)證了就判斷該主體是否有權(quán)限
- 當(dāng)我們解析出該主體的權(quán)限和我們注解的權(quán)限是一致的時(shí)候,才放行!
http://blog.csdn.net/hon_3y/article/details/70767050
流程:
這里寫圖片描述
3.1認(rèn)證的JavaBean
我們之前認(rèn)證都是放在默認(rèn)的Javabean對(duì)象上的,現(xiàn)在既然我們準(zhǔn)備學(xué)Shiro了,我們就得專業(yè)一點(diǎn),弄一個(gè)專門存儲(chǔ)認(rèn)證信息的JavaBean
/** * 用戶身份信息,存入session 由于tomcat將session會(huì)序列化在本地硬盤上,所以使用Serializable接口 * * @author Thinkpad * */public class ActiveUser implements java.io.Serializable { private String userid;//用戶id(主鍵) private String usercode;// 用戶賬號(hào) private String username;// 用戶名稱 private List<SysPermission> menus;// 菜單 private List<SysPermission> permissions;// 權(quán)限 // 省略get和set方法}
認(rèn)證的服務(wù)
@Override public ActiveUser authenticat(String userCode, String password) throws Exception { /** 認(rèn)證過(guò)程: 根據(jù)用戶身份(賬號(hào))查詢數(shù)據(jù)庫(kù),如果查詢不到用戶不存在 對(duì)輸入的密碼 和數(shù)據(jù)庫(kù)密碼 進(jìn)行比對(duì),如果一致,認(rèn)證通過(guò) */ //根據(jù)用戶賬號(hào)查詢數(shù)據(jù)庫(kù) SysUser sysUser = this.findSysUserByUserCode(userCode); if(sysUser == null){ //拋出異常 throw new CustomException(\”用戶賬號(hào)不存在\”); } //數(shù)據(jù)庫(kù)密碼 (md5密碼 ) String password_db = sysUser.getPassword(); //對(duì)輸入的密碼 和數(shù)據(jù)庫(kù)密碼 進(jìn)行比對(duì),如果一致,認(rèn)證通過(guò) //對(duì)頁(yè)面輸入的密碼 進(jìn)行md5加密 String password_input_md5 = new MD5().getMD5ofStr(password); if(!password_input_md5.equalsIgnoreCase(password_db)){ //拋出異常 throw new CustomException(\”用戶名或密碼 錯(cuò)誤\”); } //得到用戶id String userid = sysUser.getId(); //根據(jù)用戶id查詢菜單 List<SysPermission> menus =this.findMenuListByUserId(userid); //根據(jù)用戶id查詢權(quán)限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //認(rèn)證通過(guò),返回用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(userCode); activeUser.setUsername(sysUser.getUsername());//用戶名稱 //放入權(quán)限范圍的菜單和url activeUser.setMenus(menus); activeUser.setPermissions(permissions); return activeUser; }
Controller處理認(rèn)證,如果身份認(rèn)證成功,那么把認(rèn)證信息存儲(chǔ)在Session中
@requestMapping(\”/login\”) public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{ //校驗(yàn)驗(yàn)證碼,防止惡性攻擊 //從session獲取正確驗(yàn)證碼 String validateCode = (String) session.getAttribute(\”validateCode\”); //輸入的驗(yàn)證和session中的驗(yàn)證進(jìn)行對(duì)比 if(!randomcode.equals(validateCode)){ //拋出異常 throw new CustomException(\”驗(yàn)證碼輸入錯(cuò)誤\”); } //調(diào)用service校驗(yàn)用戶賬號(hào)和密碼的正確性 ActiveUser activeUser = sysService.authenticat(usercode, password); //如果service校驗(yàn)通過(guò),將用戶身份記錄到session session.setAttribute(\”activeUser\”, activeUser); //重定向到商品查詢頁(yè)面 return \”redirect:/first.action\”; }
身份認(rèn)證攔截器
//在執(zhí)行handler之前來(lái)執(zhí)行的 //用于用戶認(rèn)證校驗(yàn)、用戶權(quán)限校驗(yàn) @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到請(qǐng)求的url String url = request.getRequestURI(); //判斷是否是公開 地址 //實(shí)際開發(fā)中需要公開 地址配置在配置文件中 //從配置中取逆名訪問(wèn)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; } //執(zhí)行到這里攔截,跳轉(zhuǎn)到登陸頁(yè)面,用戶進(jìn)行身份認(rèn)證 request.getRequestDispatcher(\”/WEB-INF/jsp/login.jsp\”).forward(request, response); //如果返回false表示攔截不繼續(xù)執(zhí)行handler,如果返回true表示放行 return false; }
授權(quán)攔截器
//在執(zhí)行handler之前來(lái)執(zhí)行的 //用于用戶認(rèn)證校驗(yàn)、用戶權(quán)限校驗(yàn) @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到請(qǐng)求的url String url = request.getRequestURI(); //判斷是否是公開 地址 //實(shí)際開發(fā)中需要公開 地址配置在配置文件中 //從配置中取逆名訪問(wèn)url List<String> open_urls = ResourcesUtil.gekeyList(\”anonymousURL\”); //遍歷公開 地址,如果是公開 地址則放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //如果是公開 地址則放行 return true; } } //從配置文件中獲取公共訪問(wèn)地址 List<String> common_urls = ResourcesUtil.gekeyList(\”commonURL\”); //遍歷公用 地址,如果是公用 地址則放行 for(String common_url:common_urls){ if(url.indexOf(common_url)>=0){ //如果是公開 地址則放行 return true; } } //獲取session HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute(\”activeUser\”); //從session中取權(quán)限范圍的url List<SysPermission> permissions = activeUser.getPermissions(); for(SysPermission sysPermission:permissions){ //權(quán)限的url String permission_url = sysPermission.getUrl(); if(url.indexOf(permission_url)>=0){ //如果是權(quán)限的url 地址則放行 return true; } } //執(zhí)行到這里攔截,跳轉(zhuǎn)到無(wú)權(quán)訪問(wèn)的提示頁(yè)面 request.getRequestDispatcher(\”/WEB-INF/jsp/refuse.jsp\”).forward(request, response); //如果返回false表示攔截不繼續(xù)執(zhí)行handler,如果返回true表示放行 return false; }
攔截器配置:
<!–攔截器 –> <mvc:interceptors> <mvc:interceptor> <!– 用戶認(rèn)證攔截 –> <mvc:mapping path=\”/**\” /> <bean class=\”cn.itcast.ssm.controller.interceptor.Logininterceptor\”></bean> </mvc:interceptor> <mvc:interceptor> <!– 授權(quán)攔截 –> <mvc:mapping path=\”/**\” /> <bean class=\”cn.itcast.ssm.controller.interceptor.PermissionInterceptor\”></bean> </mvc:interceptor> </mvc:interceptors>
四、什么是Shiro
shiro是apache的一個(gè)開源框架,是一個(gè)權(quán)限管理的框架,實(shí)現(xiàn) 用戶認(rèn)證、用戶授權(quán)。
spring中有spring security (原名Acegi),是一個(gè)權(quán)限框架,它和spring依賴過(guò)于緊密,沒(méi)有shiro使用簡(jiǎn)單。
shiro不依賴于spring,shiro不僅可以實(shí)現(xiàn) web應(yīng)用的權(quán)限管理,還可以實(shí)現(xiàn)c/s系統(tǒng),分布式系統(tǒng)權(quán)限管理,shiro屬于輕量框架,越來(lái)越多企業(yè)項(xiàng)目開始使用shiro。
Shiro架構(gòu):
這里寫圖片描述
- subject:主體,可以是用戶也可以是程序,主體要訪問(wèn)系統(tǒng),系統(tǒng)需要對(duì)主體進(jìn)行認(rèn)證、授權(quán)。
- securityManager:安全管理器,主體進(jìn)行認(rèn)證和授權(quán)都 是通過(guò)securityManager進(jìn)行。
- authenticator:認(rèn)證器,主體進(jìn)行認(rèn)證最終通過(guò)authenticator進(jìn)行的。
- authorizer:授權(quán)器,主體進(jìn)行授權(quán)最終通過(guò)authorizer進(jìn)行的。
- sessionManager:web應(yīng)用中一般是用web容器對(duì)session進(jìn)行管理,shiro也提供一套session管理的方式。
- SessionDao: 通過(guò)SessionDao管理session數(shù)據(jù),針對(duì)個(gè)性化的session數(shù)據(jù)存儲(chǔ)需要使用sessionDao。
- cache Manager:緩存管理器,主要對(duì)session和授權(quán)數(shù)據(jù)進(jìn)行緩存,比如將授權(quán)數(shù)據(jù)通過(guò)cacheManager進(jìn)行緩存管理,和ehcache整合對(duì)緩存數(shù)據(jù)進(jìn)行管理。
- realm:域,領(lǐng)域,相當(dāng)于數(shù)據(jù)源,通過(guò)realm存取認(rèn)證、授權(quán)相關(guān)數(shù)據(jù)。
cryptography:密碼管理,提供了一套加密/解密的組件,方便開發(fā)。比如提供常用的散列、加/解密等功能。
- 比如md5散列算法。
五、為什么使用Shiro
我們?cè)谑褂肬RL攔截的時(shí)候,要將所有的URL都配置起來(lái),繁瑣、不易維護(hù)
而我們的Shiro實(shí)現(xiàn)系統(tǒng)的權(quán)限管理,有效提高開發(fā)效率,從而降低開發(fā)成本。
六、Shiro認(rèn)證
6.1導(dǎo)入jar包
我們使用的是Maven的坐標(biāo)就行了
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>1.2.3</version> </dependency>
當(dāng)然了,我們也可以把Shiro相關(guān)的jar包全部導(dǎo)入進(jìn)去
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version></dependency>
6.2Shiro認(rèn)證流程
這里寫圖片描述
6.2.1通過(guò)配置文件創(chuàng)建工廠
這里寫圖片描述
// 用戶登陸和退出 @Test public void testLoginAndLogout() { // 創(chuàng)建securityManager工廠,通過(guò)ini配置文件創(chuàng)建securityManager工廠 factory<SecurityManager> factory = new IniSecurityManagerFactory( \”classpath:shiro-first.ini\”); // 創(chuàng)建SecurityManager SecurityManager securityManager = factory.getInstance(); // 將securityManager設(shè)置當(dāng)前的運(yùn)行環(huán)境中 SecurityUtils.setSecurityManager(securityManager); // 從SecurityUtils里邊創(chuàng)建一個(gè)subject Subject subject = SecurityUtils.getSubject(); // 在認(rèn)證提交前準(zhǔn)備token(令牌) // 這里的賬號(hào)和密碼 將來(lái)是由用戶輸入進(jìn)去 UsernamePasswordToken token = new UsernamePasswordToken(\”zhangsan\”, \”111111\”); try { // 執(zhí)行認(rèn)證提交 subject.login(token); } catch (AuthenticationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 是否認(rèn)證通過(guò) boolean isAuthenticated = subject.isAuthenticated(); System.out.println(\”是否認(rèn)證通過(guò):\” isAuthenticated); // 退出操作 subject.logout(); // 是否認(rèn)證通過(guò) isAuthenticated = subject.isAuthenticated(); System.out.println(\”是否認(rèn)證通過(guò):\” isAuthenticated); }
這里寫圖片描述
6.3小結(jié)
ModularRealmAuthenticator作用進(jìn)行認(rèn)證,需要調(diào)用realm查詢用戶信息(在數(shù)據(jù)庫(kù)中存在用戶信息)
ModularRealmAuthenticator進(jìn)行密碼對(duì)比(認(rèn)證過(guò)程)。
realm:需要根據(jù)token中的身份信息去查詢數(shù)據(jù)庫(kù)(入門程序使用ini配置文件),如果查到用戶返回認(rèn)證信息,如果查詢不到返回null。
6.4自定義realm
從第一個(gè)認(rèn)證程序我們可以看見,我們所說(shuō)的流程,是認(rèn)證器去找realm去查詢我們相對(duì)應(yīng)的數(shù)據(jù)。而默認(rèn)的realm是直接去與配置文件來(lái)比對(duì)的,一般地,我們?cè)陂_發(fā)中都是讓realm去數(shù)據(jù)庫(kù)中比對(duì)。
因此,我們需要自定義realm
這里寫圖片描述
public class CustomRealm extends AuthorizingRealm { // 設(shè)置realm的名稱 @Override public void setName(String name) { super.setName(\”customRealm\”); } // 用于認(rèn)證 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // token是用戶輸入的 // 第一步從token中取出身份信息 String userCode = (String) token.getPrincipal(); // 第二步:根據(jù)用戶輸入的userCode從數(shù)據(jù)庫(kù)查詢 // …. // 如果查詢不到返回null //數(shù)據(jù)庫(kù)中用戶賬號(hào)是zhangsansan /*if(!userCode.equals(\”zhangsansan\”)){// return null; }*/ // 模擬從數(shù)據(jù)庫(kù)查詢到密碼 String password = \”111112\”; // 如果查詢到返回認(rèn)證信息AuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userCode, password, this.getName()); return simpleAuthenticationInfo; } // 用于授權(quán) @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; }}
6.5配置realm
需要在shiro-realm.ini配置realm注入到securityManager中。
這里寫圖片描述
6.6測(cè)試自定義realm
同上邊的入門程序,需要更改ini配置文件路徑:
同上邊的入門程序,需要更改ini配置文件路徑:Factory<SecurityManager> factory = new IniSecurityManagerFactory( \”classpath:shiro-realm.ini\”);
6.7散列算法
我們?nèi)绻續(xù)d5,我們就會(huì)知道m(xù)d5是不可逆的,但是如果設(shè)置了一些安全性比較低的密碼:111111…即時(shí)是不可逆的,但還是可以通過(guò)暴力算法來(lái)得到md5對(duì)應(yīng)的明文…
建議對(duì)md5進(jìn)行散列時(shí)加salt(鹽),進(jìn)行加密相當(dāng) 于對(duì)原始密碼 鹽進(jìn)行散列。
正常使用時(shí)散列方法:
- 在程序中對(duì)原始密碼 鹽進(jìn)行散列,將散列值存儲(chǔ)到數(shù)據(jù)庫(kù)中,并且還要將鹽也要存儲(chǔ)在數(shù)據(jù)庫(kù)中。
測(cè)試:
public class MD5Test { public static void main(String[] args) { //原始 密碼 String source = \”111111\”; //鹽 String salt = \”qwerty\”; //散列次數(shù) int hashIterations = 2; //上邊散列1次:f3694f162729b7d0254c6e40260bf15c //上邊散列2次:36f2dfa24d0a9fa97276abbe13e596fc //構(gòu)造方法中: //第一個(gè)參數(shù):明文,原始密碼 //第二個(gè)參數(shù):鹽,通過(guò)使用隨機(jī)數(shù) //第三個(gè)參數(shù):散列的次數(shù),比如散列兩次,相當(dāng) 于md5(md5(\’\’)) Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations); String password_md5 = md5Hash.toString(); System.out.println(password_md5); //第一個(gè)參數(shù):散列算法 SimpleHash simpleHash = new SimpleHash(\”md5\”, source, salt, hashIterations); System.out.println(simpleHash.toString()); }}
6.8自定義realm支持md5
自定義realm
public class CustomRealmMd5 extends AuthorizingRealm { // 設(shè)置realm的名稱 @Override public void setName(String name) { super.setName(\”customRealmMd5\”); } // 用于認(rèn)證 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // token是用戶輸入的 // 第一步從token中取出身份信息 String userCode = (String) token.getPrincipal(); // 第二步:根據(jù)用戶輸入的userCode從數(shù)據(jù)庫(kù)查詢 // …. // 如果查詢不到返回null // 數(shù)據(jù)庫(kù)中用戶賬號(hào)是zhangsansan /* * if(!userCode.equals(\”zhangsansan\”)){// return null; } */ // 模擬從數(shù)據(jù)庫(kù)查詢到密碼,散列值 String password = \”f3694f162729b7d0254c6e40260bf15c\”; // 從數(shù)據(jù)庫(kù)獲取salt String salt = \”qwerty\”; //上邊散列值和鹽對(duì)應(yīng)的明文:111111 // 如果查詢到返回認(rèn)證信息AuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userCode, password, ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; } // 用于授權(quán) @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; }}
配置文件:
這里寫圖片描述
測(cè)試:
// 自定義realm實(shí)現(xiàn)散列值匹配 @Test public void testCustomRealmMd5() { // 創(chuàng)建securityManager工廠,通過(guò)ini配置文件創(chuàng)建securityManager工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory( \”classpath:shiro-realm-md5.ini\”); // 創(chuàng)建SecurityManager SecurityManager securityManager = factory.getInstance(); // 將securityManager設(shè)置當(dāng)前的運(yùn)行環(huán)境中 SecurityUtils.setSecurityManager(securityManager); // 從SecurityUtils里邊創(chuàng)建一個(gè)subject Subject subject = SecurityUtils.getSubject(); // 在認(rèn)證提交前準(zhǔn)備token(令牌) // 這里的賬號(hào)和密碼 將來(lái)是由用戶輸入進(jìn)去 UsernamePasswordToken token = new UsernamePasswordToken(\”zhangsan\”, \”222222\”); try { // 執(zhí)行認(rèn)證提交 subject.login(token); } catch (AuthenticationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 是否認(rèn)證通過(guò) boolean isAuthenticated = subject.isAuthenticated(); System.out.println(\”是否認(rèn)證通過(guò):\” isAuthenticated); }
七、總結(jié)
- 用戶認(rèn)證和用戶授權(quán)是Shiro的基礎(chǔ),用戶認(rèn)證其實(shí)上就是登陸操作、用戶授權(quán)實(shí)際上就是對(duì)資源攔截的操作。
- 權(quán)限管理的模型一般我們都將資源放在權(quán)限表中進(jìn)行管理起來(lái)。
- 我們可以基于角色攔截,也可以基于資源攔截。要是基于角色攔截的話,那么如果角色的權(quán)限發(fā)生變化了,那就需要修改代碼了。推薦使用基于資源進(jìn)行攔截
- 這次URL攔截,我們使用一個(gè)JavaBean來(lái)封裝所有的認(rèn)證信息。當(dāng)用戶登陸了之后,我們就把用戶對(duì)菜單欄的訪問(wèn)、對(duì)資源的訪問(wèn)權(quán)限都封裝到該JavaBean中
- 當(dāng)使用攔截器進(jìn)行用戶認(rèn)證的時(shí)候,我們只要判斷Session域有沒(méi)有JavaBen對(duì)象即可了。
- 當(dāng)時(shí)候攔截器進(jìn)行用戶授權(quán)的時(shí)候,我們要判斷JavaBean中的權(quán)限是否能夠訪問(wèn)該資源。
- 以前URL攔截的方式需要把所有的URL都在數(shù)據(jù)庫(kù)進(jìn)行管理。非常麻煩,不易維護(hù)。
- 我們希望Shiro去認(rèn)證的時(shí)候是通過(guò)realm去數(shù)據(jù)庫(kù)查詢數(shù)據(jù)的。而我們r(jià)eaml默認(rèn)是查詢配置文件的數(shù)據(jù)的。
- 因此,我們需要自定義reaml,使得它是去數(shù)據(jù)庫(kù)查詢數(shù)據(jù)。只要繼承AuthorizingRealm類就行了。
- 當(dāng)然了,自定義后的reaml也需要在配置文件中寫上我們的自定義reaml的位置的。
- 散列算法就是為了讓密碼不被別人給破解。我們可對(duì)原始的密碼加鹽再進(jìn)行散列,這就加大了破解的難度了。
- 自定義的reaml也是支持散列算法的,相同的,還是需要我們?cè)谂渲梦募信渲靡幌戮秃昧恕?/li>
作者:Java3y原文:轉(zhuǎn)載自公眾號(hào),Java3y,已獲作者授權(quán)
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。