前言
本文主要講解的知識點(diǎn)有以下:
- 權(quán)限管理的基礎(chǔ)知識
- 模型
- 粗粒度和細(xì)粒度的概念
- 回顧URL攔截的實(shí)現(xiàn)
- Shiro的介紹與簡單入門
一我擂、Shiro基礎(chǔ)知識
在學(xué)習(xí)Shiro這個(gè)框架之前择吊,首先我們要先了解Shiro需要的基礎(chǔ)知識:權(quán)限管理
1.1什么是權(quán)限管理促绵?
只要有用戶參與的系統(tǒng)一般都要有權(quán)限管理咒循,權(quán)限管理實(shí)現(xiàn)對用戶訪問系統(tǒng)的控制,按照安全規(guī)則或者安全策略控制用戶可以訪問而且只能訪問自己被授權(quán)的資源绞愚。
對權(quán)限的管理又分為兩大類別:
- 用戶認(rèn)證
- 用戶授權(quán)
1.1.1用戶認(rèn)證
用戶認(rèn)證叙甸,用戶去訪問系統(tǒng),系統(tǒng)要驗(yàn)證用戶身份的合法性
最常用的用戶身份驗(yàn)證的方法:1位衩、用戶名密碼方式裆蒸、2、指紋打卡機(jī)糖驴、3僚祷、基于證書驗(yàn)證方法佛致。。系統(tǒng)驗(yàn)證用戶身份合法辙谜,用戶方可訪問系統(tǒng)的資源俺榆。
舉個(gè)例子:
- 當(dāng)我們輸入了自己的淘寶的賬戶和密碼,才能打開購物車
用戶認(rèn)證的流程:
- 判斷該資源能否不認(rèn)證就能訪問【登陸頁面装哆、首頁】
- 如果該資源需要認(rèn)證后才能訪問罐脊,那么判斷該訪問者是否認(rèn)證了
- 如果還沒有認(rèn)證,那么需要返回到【登陸頁面】進(jìn)行認(rèn)證
- 認(rèn)證通過后才能訪問資源
從用戶認(rèn)證我們可以抽取出這么幾個(gè)概念
- subject主體:理解為用戶,可能是程序蜕琴,都要去訪問系統(tǒng)的資源萍桌,系統(tǒng)需要對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)凸郑,簡單理解為訪問控制裳食,在用戶認(rèn)證通過后,系統(tǒng)對用戶訪問資源進(jìn)行控制线椰,用戶具有資源的訪問權(quán)限方可訪問。
用戶授權(quán)的流程
- 到達(dá)了用戶授權(quán)環(huán)節(jié)尘盼,當(dāng)然是需要用戶認(rèn)證之后了
- 用戶訪問資源憨愉,系統(tǒng)判斷該用戶是否有權(quán)限去操作該資源
- 如果該用戶有權(quán)限才能夠訪問,如果沒有權(quán)限就不能訪問了
授權(quán)的過程可以簡單理解為:主體認(rèn)證之后卿捎,系統(tǒng)進(jìn)行訪問控制
subject必須具備資源的訪問權(quán)限才可訪問該資源..
權(quán)限/許可(permission) :針對資源的權(quán)限或許可配紫,subject具有permission訪問資源,如何訪問/操作需要定義permission午阵,權(quán)限比如:用戶添加躺孝、用戶修改、商品刪除
資源可以分為兩種
- 資源類型:系統(tǒng)的用戶信息就是資源類型底桂,相當(dāng)于java類植袍。
- 資源實(shí)例:系統(tǒng)中id為001的用戶就是資源實(shí)例,相當(dāng)于new的java對象籽懦。
1.2權(quán)限管理模型
一般地于个,我們可以抽取出這么幾個(gè)模型:
- 主體(賬號、密碼)
- 資源(資源名稱暮顺、訪問地址)
- 權(quán)限(權(quán)限名稱厅篓、資源id)
- 角色(角色名稱)
- 角色和權(quán)限關(guān)系(角色id秀存、權(quán)限id)
- 主體和角色關(guān)系(主體id、角色id)
通常企業(yè)開發(fā)中將資源和權(quán)限表合并為一張權(quán)限表羽氮,如下:
- 資源(資源名稱或链、訪問地址)
- 權(quán)限(權(quán)限名稱、資源id)
合并為:
- 權(quán)限(權(quán)限名稱档押、資源名稱澳盐、資源訪問地址)
1.3分配權(quán)限
用戶需要分配相應(yīng)的權(quán)限才可訪問相應(yīng)的資源。權(quán)限是對于資源的操作許可汇荐。
通常給用戶分配資源權(quán)限需要將權(quán)限信息持久化洞就,比如存儲在關(guān)系數(shù)據(jù)庫中。把用戶信息掀淘、權(quán)限管理旬蟋、用戶分配的權(quán)限信息寫到數(shù)據(jù)庫(權(quán)限數(shù)據(jù)模型)
1.3.1基于角色訪問控制
RBAC(role based access control),基于角色的訪問控制革娄。
//如果該user是部門經(jīng)理則可以訪問if中的代碼
if(user.hasRole('部門經(jīng)理')){
//系統(tǒng)資源內(nèi)容
//用戶報(bào)表查看
}
角色針對人劃分的倾贰,人作為用戶在系統(tǒng)中屬于活動內(nèi)容,如果該 角色可以訪問的資源出現(xiàn)變更拦惋,需要修改你的代碼了匆浙,
if(user.hasRole('部門經(jīng)理') || user.hasRole('總經(jīng)理') ){
//系統(tǒng)資源內(nèi)容
//用戶報(bào)表查看
}
基于角色的訪問控制是不利于系統(tǒng)維護(hù)(可擴(kuò)展性不強(qiáng))。
1.3.2基于資源的訪問控制
RBAC(Resource based access control)厕妖,基于資源的訪問控制首尼。
資源在系統(tǒng)中是不變的,比如資源有:類中的方法言秸,頁面中的按鈕软能。
對資源的訪問需要具有permission權(quán)限,代碼可以寫為:
if(user.hasPermission ('用戶報(bào)表查看(權(quán)限標(biāo)識符)')){
//系統(tǒng)資源內(nèi)容
//用戶報(bào)表查看
}
建議使用基于資源的訪問控制實(shí)現(xiàn)權(quán)限管理举畸。
二查排、 粗粒度和細(xì)粒度權(quán)限
細(xì)粒度權(quán)限管理:對資源實(shí)例的權(quán)限管理。資源實(shí)例就資源類型的具體化抄沮,比如:用戶id為001的修改連接跋核,1110班的用戶信息、行政部的員工叛买。細(xì)粒度權(quán)限管理就是數(shù)據(jù)級別的權(quán)限管理砂代。
粗粒度權(quán)限管理比如:超級管理員可以訪問戶添加頁面、用戶信息等全部頁面率挣。部門管理員可以訪問用戶信息頁面包括 頁面中所有按鈕泊藕。
粗粒度和細(xì)粒度例子:
系統(tǒng)有一個(gè)用戶列表查詢頁面,對用戶列表查詢分權(quán)限,
如果粗顆粒管理娃圆,張三和李四都有用戶列表查詢的權(quán)限玫锋,張三和李四都可以訪問用戶列表查詢。
進(jìn)一步進(jìn)行細(xì)顆粒管理讼呢,張三(行政部)和李四(開發(fā)部)只可以查詢自己本部門的用戶信息撩鹿。
張三只能查看行政部 的用戶信息,李四只能查看開發(fā)部門的用戶信息悦屏。
細(xì)粒度權(quán)限管理就是數(shù)據(jù)級別的權(quán)限管理节沦。
2.1如何實(shí)現(xiàn)粗粒度權(quán)限管理?
粗粒度權(quán)限管理比較容易將權(quán)限管理的代碼抽取出來在系統(tǒng)架構(gòu)級別統(tǒng)一處理础爬。比如:通過springmvc的攔截器實(shí)現(xiàn)授權(quán)甫贯。
對細(xì)粒度權(quán)限管理在數(shù)據(jù)級別是沒有共性可言,針對細(xì)粒度權(quán)限管理就是系統(tǒng)業(yè)務(wù)邏輯的一部分看蚜,在業(yè)務(wù)層去處理相對比較簡單
比如:部門經(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ā)中比較常用的一種方式音诫。
對于web系統(tǒng)惨奕,通過filter過慮器實(shí)現(xiàn)url攔截,也可以springmvc的攔截器實(shí)現(xiàn)基于url的攔截竭钝。
2.2.2使用權(quán)限管理框架實(shí)現(xiàn)
對于粗粒度權(quán)限管理梨撞,建議使用優(yōu)秀權(quán)限管理框架來實(shí)現(xiàn),節(jié)省開發(fā)成功香罐,提高開發(fā)效率卧波。
shiro就是一個(gè)優(yōu)秀權(quán)限管理框架。
三穴吹、回顧URL攔截
我們在學(xué)習(xí)的路途上也是使用過幾次URL對權(quán)限進(jìn)行攔截的
當(dāng)時(shí)我們做了權(quán)限的增刪該查的管理系統(tǒng)幽勒,但是在權(quán)限表中是沒有把資源添加進(jìn)去嗜侮,我們使用的是Map集合來進(jìn)行替代的港令。http://blog.csdn.net/hon_3y/article/details/61926175
隨后,我們學(xué)習(xí)了動態(tài)代理和注解锈颗,我們也做了一個(gè)基于注解的攔截
- 在Controller得到service對象的時(shí)候顷霹,service工廠返回的是一個(gè)動態(tài)代理對象回去
- Controller拿著代理對象去調(diào)用方法,代理對象就會去解析該方法上是否有注解
- 如果有注解击吱,那么就需要我們進(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對象上的朵纷,現(xiàn)在既然我們準(zhǔn)備學(xué)Shiro了炭臭,我們就得專業(yè)一點(diǎn),弄一個(gè)專門存儲認(rèn)證信息的JavaBean
/**
* 用戶身份信息袍辞,存入session 由于tomcat將session會序列化在本地硬盤上鞋仍,所以使用Serializable接口
*
* @author Thinkpad
*
*/
public class ActiveUser implements java.io.Serializable {
private String userid;//用戶id(主鍵)
private String usercode;// 用戶賬號
private String username;// 用戶名稱
private List<SysPermission> menus;// 菜單
private List<SysPermission> permissions;// 權(quán)限
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsercode() {
return usercode;
}
public void setUsercode(String usercode) {
this.usercode = usercode;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public List<SysPermission> getMenus() {
return menus;
}
public void setMenus(List<SysPermission> menus) {
this.menus = menus;
}
public List<SysPermission> getPermissions() {
return permissions;
}
public void setPermissions(List<SysPermission> permissions) {
this.permissions = permissions;
}
}
認(rèn)證的服務(wù)
@Override
public ActiveUser authenticat(String userCode, String password)
throws Exception {
/**
認(rèn)證過程:
根據(jù)用戶身份(賬號)查詢數(shù)據(jù)庫,如果查詢不到用戶不存在
對輸入的密碼 和數(shù)據(jù)庫密碼 進(jìn)行比對搅吁,如果一致威创,認(rèn)證通過
*/
//根據(jù)用戶賬號查詢數(shù)據(jù)庫
SysUser sysUser = this.findSysUserByUserCode(userCode);
if(sysUser == null){
//拋出異常
throw new CustomException("用戶賬號不存在");
}
//數(shù)據(jù)庫密碼 (md5密碼 )
String password_db = sysUser.getPassword();
//對輸入的密碼 和數(shù)據(jù)庫密碼 進(jìn)行比對,如果一致谎懦,認(rèn)證通過
//對頁面輸入的密碼 進(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)證通過肚豺,返回用戶身份信息
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)證信息存儲在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)行對比
if(!randomcode.equals(validateCode)){
//拋出異常
throw new CustomException("驗(yàn)證碼輸入錯(cuò)誤");
}
//調(diào)用service校驗(yàn)用戶賬號和密碼的正確性
ActiveUser activeUser = sysService.authenticat(usercode, password);
//如果service校驗(yàn)通過,將用戶身份記錄到session
session.setAttribute("activeUser", activeUser);
//重定向到商品查詢頁面
return "redirect:/first.action";
}
身份認(rèn)證攔截器
//在執(zhí)行handler之前來執(zhí)行的
//用于用戶認(rèn)證校驗(yàn)寞奸、用戶權(quán)限校驗(yàn)
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開 地址
//實(shí)際開發(fā)中需要公開 地址配置在配置文件中
//從配置中取逆名訪問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)到登陸頁面枪萄,用戶進(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之前來執(zhí)行的
//用于用戶認(rèn)證校驗(yàn)、用戶權(quán)限校驗(yàn)
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開 地址
//實(shí)際開發(fā)中需要公開 地址配置在配置文件中
//從配置中取逆名訪問url
List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
//遍歷公開 地址瓷翻,如果是公開 地址則放行
for(String open_url:open_urls){
if(url.indexOf(open_url)>=0){
//如果是公開 地址則放行
return true;
}
}
//從配置文件中獲取公共訪問地址
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)到無權(quán)訪問的提示頁面
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依賴過于緊密,沒有shiro使用簡單孝常。 shiro不依賴于spring旗们,shiro不僅可以實(shí)現(xiàn) web應(yīng)用的權(quán)限管理,還可以實(shí)現(xiàn)c/s系統(tǒng)构灸,分布式系統(tǒng)權(quán)限管理上渴,shiro屬于輕量框架,越來越多企業(yè)項(xiàng)目開始使用shiro。
Shiro架構(gòu):
- subject:主體稠氮,可以是用戶也可以是程序曹阔,主體要訪問系統(tǒng),系統(tǒng)需要對主體進(jìn)行認(rèn)證隔披、授權(quán)次兆。
- securityManager:安全管理器,主體進(jìn)行認(rèn)證和授權(quán)都 是通過securityManager進(jìn)行锹锰。
- authenticator:認(rèn)證器芥炭,主體進(jìn)行認(rèn)證最終通過authenticator進(jìn)行的。
- authorizer:授權(quán)器恃慧,主體進(jìn)行授權(quán)最終通過authorizer進(jìn)行的园蝠。
- sessionManager:web應(yīng)用中一般是用web容器對session進(jìn)行管理,shiro也提供一套session管理的方式痢士。
- SessionDao: 通過SessionDao管理session數(shù)據(jù)彪薛,針對個(gè)性化的session數(shù)據(jù)存儲需要使用sessionDao。
- cache Manager:緩存管理器怠蹂,主要對session和授權(quán)數(shù)據(jù)進(jìn)行緩存善延,比如將授權(quán)數(shù)據(jù)通過cacheManager進(jìn)行緩存管理,和ehcache整合對緩存數(shù)據(jù)進(jìn)行管理城侧。
- realm:域易遣,領(lǐng)域,相當(dāng)于數(shù)據(jù)源嫌佑,通過realm存取認(rèn)證豆茫、授權(quán)相關(guān)數(shù)據(jù)。
cryptography:密碼管理屋摇,提供了一套加密/解密的組件揩魂,方便開發(fā)。比如提供常用的散列炮温、加/解密等功能火脉。
- 比如md5散列算法。
五柒啤、為什么使用Shiro
我們在使用URL攔截的時(shí)候倦挂,要將所有的URL都配置起來,繁瑣白修、不易維護(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通過配置文件創(chuàng)建工廠
// 用戶登陸和退出
@Test
public void testLoginAndLogout() {
// 創(chuàng)建securityManager工廠祖很,通過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(令牌)
// 這里的賬號和密碼 將來是由用戶輸入進(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)證通過
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否認(rèn)證通過:" + isAuthenticated);
// 退出操作
subject.logout();
// 是否認(rèn)證通過
isAuthenticated = subject.isAuthenticated();
System.out.println("是否認(rèn)證通過:" + isAuthenticated);
}
6.3小結(jié)
ModularRealmAuthenticator作用進(jìn)行認(rèn)證笛丙,需要調(diào)用realm查詢用戶信息(在數(shù)據(jù)庫中存在用戶信息)ModularRealmAuthenticator進(jìn)行密碼對比(認(rèn)證過程)。 realm:需要根據(jù)token中的身份信息去查詢數(shù)據(jù)庫(入門程序使用ini配置文件)假颇,如果查到用戶返回認(rèn)證信息胚鸯,如果查詢不到返回null。
6.4自定義realm
從第一個(gè)認(rèn)證程序我們可以看見笨鸡,我們所說的流程姜钳,是認(rèn)證器去找realm去查詢我們相對應(yīng)的數(shù)據(jù)。而默認(rèn)的realm是直接去與配置文件來比對的形耗,一般地哥桥,我們在開發(fā)中都是讓realm去數(shù)據(jù)庫中比對。 因此激涤,我們需要自定義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ù)庫查詢
// ....
// 如果查詢不到返回null
//數(shù)據(jù)庫中用戶賬號是zhangsansan
/*if(!userCode.equals("zhangsansan")){//
return null;
}*/
// 模擬從數(shù)據(jù)庫查詢到密碼
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測試自定義realm
同上邊的入門程序,需要更改ini配置文件路徑:
同上邊的入門程序倦踢,需要更改ini配置文件路徑:
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-realm.ini");
6.7散列算法
我們?nèi)绻續(xù)d5送滞,我們就會知道m(xù)d5是不可逆的,但是如果設(shè)置了一些安全性比較低的密碼:111111...即時(shí)是不可逆的辱挥,但還是可以通過暴力算法來得到md5對應(yīng)的明文...
建議對md5進(jìn)行散列時(shí)加salt(鹽)犁嗅,進(jìn)行加密相當(dāng) 于對原始密碼+鹽進(jìn)行散列。\
正常使用時(shí)散列方法:
- 在程序中對原始密碼+鹽進(jìn)行散列晤碘,將散列值存儲到數(shù)據(jù)庫中愧哟,并且還要將鹽也要存儲在數(shù)據(jù)庫中。
測試:
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ù):鹽蕊梧,通過使用隨機(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ù)庫查詢
// ....
// 如果查詢不到返回null
// 數(shù)據(jù)庫中用戶賬號是zhangsansan
/*
* if(!userCode.equals("zhangsansan")){// return null; }
*/
// 模擬從數(shù)據(jù)庫查詢到密碼,散列值
String password = "f3694f162729b7d0254c6e40260bf15c";
// 從數(shù)據(jù)庫獲取salt
String salt = "qwerty";
//上邊散列值和鹽對應(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;
}
}
配置文件:
測試:
// 自定義realm實(shí)現(xiàn)散列值匹配
@Test
public void testCustomRealmMd5() {
// 創(chuàng)建securityManager工廠肥矢,通過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(令牌)
// 這里的賬號和密碼 將來是由用戶輸入進(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)證通過
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否認(rèn)證通過:" + isAuthenticated);
}
七、總結(jié)
- 用戶認(rèn)證和用戶授權(quán)是Shiro的基礎(chǔ)叠洗,用戶認(rèn)證其實(shí)上就是登陸操作甘改、用戶授權(quán)實(shí)際上就是對資源攔截的操作。
- 權(quán)限管理的模型一般我們都將資源放在權(quán)限表中進(jìn)行管理起來灭抑。
- 我們可以基于角色攔截十艾,也可以基于資源攔截。要是基于角色攔截的話腾节,那么如果角色的權(quán)限發(fā)生變化了忘嫉,那就需要修改代碼了荤牍。推薦使用基于資源進(jìn)行攔截
- 這次URL攔截,我們使用一個(gè)JavaBean來封裝所有的認(rèn)證信息庆冕。當(dāng)用戶登陸了之后康吵,我們就把用戶對菜單欄的訪問、對資源的訪問權(quán)限都封裝到該JavaBean中
- 當(dāng)使用攔截器進(jìn)行用戶認(rèn)證的時(shí)候访递,我們只要判斷Session域有沒有JavaBen對象即可了晦嵌。
- 當(dāng)時(shí)候攔截器進(jìn)行用戶授權(quán)的時(shí)候,我們要判斷JavaBean中的權(quán)限是否能夠訪問該資源拷姿。
- 以前URL攔截的方式需要把所有的URL都在數(shù)據(jù)庫進(jìn)行管理惭载。非常麻煩,不易維護(hù)响巢。
- 我們希望Shiro去認(rèn)證的時(shí)候是通過realm去數(shù)據(jù)庫查詢數(shù)據(jù)的棕兼。而我們r(jià)eaml默認(rèn)是查詢配置文件的數(shù)據(jù)的。
- 因此抵乓,我們需要自定義reaml伴挚,使得它是去數(shù)據(jù)庫查詢數(shù)據(jù)。只要繼承AuthorizingRealm類就行了灾炭。
- 當(dāng)然了茎芋,自定義后的reaml也需要在配置文件中寫上我們的自定義reaml的位置的。
- 散列算法就是為了讓密碼不被別人給破解蜈出。我們可對原始的密碼加鹽再進(jìn)行散列田弥,這就加大了破解的難度了。
- 自定義的reaml也是支持散列算法的铡原,相同的偷厦,還是需要我們在配置文件中配置一下就好了。
如果文章有錯(cuò)的地方歡迎指正燕刻,大家互相交流只泼。習(xí)慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學(xué)卵洗,可以關(guān)注微信公眾號:Java3y