Shiro的用法以及配置

shiro認(rèn)證的流程(非常重要)

shiro認(rèn)證流程.png

認(rèn)證流程.png

為什么要重寫realm?如何重寫realm?

在執(zhí)行調(diào)用subject.login(token)方法后,會(huì)把subject以及token都傳進(jìn)去
(subject是從環(huán)境中取出的扑媚,也就是說subject是可以代表當(dāng)前用戶所處的上下文環(huán)境扯饶,也即是說可以拿到當(dāng)前環(huán)境的realm的真實(shí)數(shù)據(jù));
程序會(huì)先判斷賬號(hào)藻雪,再判斷密碼砰琢。

  • 為什么要重寫realm?
    因?yàn)樵趙eb開發(fā)中胡桨,我們需要realm內(nèi)的真實(shí)數(shù)據(jù)是我們自己查出來的數(shù)據(jù)庫中的數(shù)據(jù)扣孟,
    包含我們自己的邏輯多矮,所以我們需要重寫realm以存放我們的數(shù)據(jù)

  • 如何去重寫realm?

Realm的繼承體系

Realm的繼承體系.png

使用AuthorizingRealm來繼承
shrio進(jìn)行認(rèn)證的底層的邏輯主要在realm.doGetAuthenticationInfo(token)中,原生的方法里
(在一開始創(chuàng)建securityManager實(shí)例對(duì)象的時(shí)候哈打,會(huì)將用戶指定的realm(ini方式)加載進(jìn)環(huán)境)
有個(gè)getUser->realm.getUser(upToken.getUsername())(realm在初始化securityManager的時(shí)候就加載進(jìn)內(nèi)存塔逃,所以這里的數(shù)據(jù)源是從環(huán)境中來的)方法來獲得account(這就是一個(gè)AuthenticationInfo),如果此時(shí)account為空,那么就不再判斷密碼而是直接報(bào)錯(cuò)出來料仗,
如果這個(gè)account有值湾盗,那么程序會(huì)拿著這個(gè)account繼續(xù)往下判斷密碼
而我們的邏輯主要在于一開始的數(shù)據(jù)從哪來,怎么進(jìn)行第一步的判斷賬號(hào)
所以 我們重寫realm主要就是重寫doGetAuthenticationInfo(token)方法立轧,在該方法中使用service來從數(shù)據(jù)庫獲得數(shù)據(jù)格粪,并進(jìn)行初次判斷躏吊。

因?yàn)樵膔ealm里doGetAuthenticationInfo(token)方法中只進(jìn)行用戶賬號(hào)的判斷,然后將account(info)返回帐萎,交給后續(xù)程序處理(realm.assertCredentialsMatch(token, info))比伏,這個(gè)info是包含數(shù)據(jù)源中的信息的,相當(dāng)于一個(gè)標(biāo)準(zhǔn)疆导,用來被比較赁项。)
所以在我們重寫的只需要把一個(gè)包含我們doGetAuthenticationInfo(token)方法中,只需要把標(biāo)準(zhǔn)的密碼封裝到一個(gè)info對(duì)象即可澈段,這里自己
new SimpleAuthenticationInfo(employee,employee.getPassword(),getName());將其返回就好悠菜,剩下的交給shrio。

@Autowired
private IEmployeeService employeeService;

/*通過注入的方式給realm設(shè)置憑證匹配器*/
@Autowired
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
    super.setCredentialsMatcher(credentialsMatcher);
}

//認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
    Employee employee = employeeService.getByUsername((String)token.getPrincipal());
    if(employee!=null){
        return new SimpleAuthenticationInfo(employee,employee.getPassword(), ByteSource.Util.bytes(employee.getName()),getName());
    }
    return null;
}
  • 重寫了realm败富,我們就需要讓shiro知道使用我們自定義的數(shù)據(jù)源
    JavaSE:通過ini配置文件告知
    JavaEE: 通過spring的配置文件悔醋,在安全管理器中的bean配置
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myWebRealm"/>
</bean>

怎么使用Spring和shrio進(jìn)行認(rèn)證

在JavaSE中,我們只是在做簡單的驗(yàn)證模擬兽叮,這時(shí)我們已經(jīng)是在做登錄操作了芬骄!
那么在JavaEE中,我們要怎么判斷用戶的哪些行為是登錄操作?

  • 通過過濾器判斷用戶的哪些行為是登錄操作(即什么時(shí)候來進(jìn)行登錄認(rèn)證)
    所有的請(qǐng)求都需要過濾器鹦聪,所以shrio使用的是過濾器(不要認(rèn)為也是攔截器账阻,攔截器是SpringMVC的東西)

那么就像我們以前寫原生的servlet一樣,我們要把filter(那么現(xiàn)在我們需要用到這個(gè)shiroFilter已經(jīng)寫好椎麦,我們直接用)交給tomcat來管理,此時(shí)在web.xml來配置filter(DelegatingFilterProxy),那么現(xiàn)在我們需要用到這個(gè)shiroFilter,顯然材彪,有了Spring观挎,我們不可能自己創(chuàng)建,而且這個(gè)filter也就是單例就好

配置Spring配置文件

  • 把這個(gè)filter交給Spring管理段化,通過ShiroFilterFactoryBean來創(chuàng)建這個(gè)ShiroFilter嘁捷。里面可配置(anon,logout,authc)等多個(gè)過濾器。

  • 配置好了過濾器显熏,再不妨想想在JavaSE中我們是怎么進(jìn)行登錄認(rèn)證的?
    我們先是進(jìn)行安全管理器的設(shè)置雄嚣,告知當(dāng)前環(huán)境用的是什么管理器,所以我們也需要在spring中配置安全管理器喘蟆。上面講到缓升,通過源碼分析發(fā)現(xiàn)自定義數(shù)據(jù)源是在創(chuàng)建securityManager實(shí)例對(duì)象時(shí)加載的,所以在spring配置
    securityManager的時(shí)候蕴轨,我們要告知安全管理器是用哪個(gè)數(shù)據(jù)源港谊。配置property

  • 過濾中執(zhí)行認(rèn)證操作
    過濾功能有了,認(rèn)證功能也有了此時(shí)我們就是需要在過濾的時(shí)候進(jìn)行認(rèn)證操作橙弱,也就是結(jié)合這個(gè)兩個(gè)功能歧寺,這是只需要將filter和securityManager配置關(guān)聯(lián)即可

<!--注意:名字必須要和web.xml中配置的名字一致-->
    <!-- 定義ShiroFilter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.html"/>
    <property name="filterChainDefinitions">
        <value>
            /js/**=anon
            /bootstrap-3.3.7-dist/**=anon
            /jQuery/**=anon
            /images/**=anon
            /css/**=anon
            /style/**=anon
            /logout.do=logout
            /**=authc
        </value>
     </property>
    <property name="filters">
        <map>
            <entry key="authc" value-ref="myCRMFormFilter"/>
        </map>
    </property>
</bean>

<!-- 配置安全管理器SecurityManager 在web環(huán)境下使用默認(rèn)web安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myWebRealm"/>
</bean>

登錄表單提交的用戶名和密碼名字必須是username和passord燥狰,通過源碼發(fā)現(xiàn)其底層就是req.getParameter("username")

授權(quán)操作

授權(quán)操作的整體實(shí)現(xiàn)和認(rèn)證差不多,需要使用到我們自己的業(yè)務(wù)和數(shù)據(jù)源斜筐,在web環(huán)境下開發(fā)就需要使用我們自己定義的數(shù)據(jù)源龙致,同樣還是繼承AuthorizingRealm,重寫其中的doGetAuthorizationInfo方法
在這里我們不需要判斷權(quán)限顷链,只需要將用戶的權(quán)限和角色查出目代,丟進(jìn)info里(new SimpleAuthorizationInfo())即可

//授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //創(chuàng)建一個(gè)空的AuthorizationInfo
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //拿到當(dāng)前用戶
    Employee employee = (Employee)principalCollection.getPrimaryPrincipal();
    //判斷是否是超級(jí)管理員
    if(employee.getAdmin()){
        //給管理員設(shè)置用戶信息,并且可以查詢所有權(quán)限
        info.addRole("admin");
        info.addStringPermission("*:*");
        return info;
    }else{
        //從數(shù)據(jù)庫中查到所對(duì)應(yīng)的角色和權(quán)限
        List<String> roles = employeeService.getRolesByEmployeeId(employee.getId());
        Set<String> permissions = employeeService.getPermissionsByEmployeeId(employee.getId());
        info.addRoles(roles);
        info.addStringPermissions(permissions);
        return info;
    }
}

怎么去判斷權(quán)限

就像我們之前做RBAC的一樣蕴潦,既然要有權(quán)限控制像啼,那么資源所對(duì)應(yīng)的權(quán)限是我們規(guī)定的。

  • 加載權(quán)限
    加載權(quán)限也是將Controller中貼有注解(@RequiresPermissions)的方法掃描出來存進(jìn)數(shù)據(jù)庫中潭苞,同樣也是通過注入的方式拿到Spring容器對(duì)象
    不同點(diǎn)在于:
  1. 使用Shiro時(shí)忽冻,使用容器對(duì)象的getBeansWithAnnotation()獲得的controller是可以包括貼有@Controller注解的類的子類的 (這里的意思是說,Shrio會(huì)自動(dòng)將貼有標(biāo)簽的Controller類動(dòng)態(tài)生成相應(yīng)的代理類此疹,而@Controller這個(gè)注解是沒有繼承的僧诚,但是SpringMVC還是能查找到,并且如果有子類只會(huì)找子類而不會(huì)找其父類) 然后我們要判斷這些字節(jié)碼對(duì)象是否是屬于cglib的代理類 (AopUtils.isCglibProxy(controller))

  2. 再用這些判斷后的字節(jié)碼對(duì)象獲得其父類字節(jié)碼(controller.getClass().getSuperclass())(@RequiresPermissions標(biāo)簽不繼承)找到貼有這些注解的方法,獲取其方法體上的注解字節(jié)碼然后拿到內(nèi)容,存進(jìn)數(shù)據(jù)庫中

  • 取出權(quán)限并存入作用域中
    使用Shiro可以很方便的做到這一步蝗碎,
    就像上面說的湖笨,只需要通過principalCollection.getPrimaryPrincipal()拿到當(dāng)前身份信息,也就是認(rèn)證過的用戶蹦骑,然后把權(quán)限從數(shù)據(jù)庫查到慈省,丟到info即可
    與原來RBAC的不同在于
    RBAC需要是用RequestContextHolder.getRequestAttributes()方法拿到session,然后再丟進(jìn)session眠菇,這樣其實(shí)很麻煩边败。

總的來說

Shiro是先進(jìn)行認(rèn)證,認(rèn)證通過后進(jìn)行授權(quán)捎废,什么時(shí)候校驗(yàn)權(quán)限笑窜?訪問方法的時(shí)候,代理類增強(qiáng)的方法會(huì)去檢驗(yàn)

一些配置

  • 在認(rèn)證的時(shí)候會(huì)用到MD5加密登疗,因?yàn)榇孢M(jìn)數(shù)據(jù)庫的密碼是通過加密的排截,而用戶登錄表單傳過來的密碼也要相同規(guī)則加密才行,這時(shí)我們?cè)跀?shù)據(jù)源中只將鹽丟進(jìn)info里面辐益,讓shiro后續(xù)進(jìn)行加密即可断傲,那么實(shí)行什么方式的加密是我們配置的,在shiro.xml中配置,然后配置給自定義中的setCredentialsMatcher方法
<!--配置憑證匹配器智政,并將其設(shè)置給realm(通過注入的方式)-->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <property name="hashAlgorithmName" value="md5"/>
</bean>
  • 在使用shiro驗(yàn)證權(quán)限時(shí)艳悔,因?yàn)槭莝hiro自動(dòng)生成的動(dòng)態(tài)代理類,使用的AoP織入女仰,所以我們要加上AoP的配置(事務(wù)時(shí)已經(jīng)加過了),又因?yàn)樵诠δ芗訌?qiáng)中要知道當(dāng)前的安全管理器猜年,才能獲取到數(shù)據(jù)源等信息抡锈,所以我們需要指明數(shù)據(jù)源
<!--配置權(quán)限AoP織入增強(qiáng)功能-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

緩存管理器

使用shiro自帶的緩存管理器,這時(shí)權(quán)限和角色信息(shrio只負(fù)責(zé)這兩塊乔外,所以也只緩存這兩塊床三,不需另外指明)緩存到內(nèi)存中,這樣就不會(huì)刷新頁面訪問同樣的資源時(shí)還要執(zhí)行數(shù)據(jù)源中的授權(quán)方法杨幼,這樣就不用再發(fā)SQL了

<!-- 緩存管理器開始 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManager" ref="ehCacheManager"/>
</bean>
<bean id="ehCacheManager" class ="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:shiro-ehcache.xml" />
    <property name="shared" value="true"/>
</bean>

shiro和freemark的兼容配置(在FreeMark中使用Shiro標(biāo)簽)

要增強(qiáng)FreeMark的功能撇簿,而又要按照FreeMark的規(guī)范,這時(shí)我們可以繼承FreeMark的類再重寫自己的方法差购,這里我們需要繼承FreeMark的配置類四瘫,拓展Shiro的便簽類(記得加依賴)

public class MyCRMFreeMarkerConfig extends FreeMarkerConfigurer {
@Override
public void afterPropertiesSet() throws IOException, TemplateException {
    super.afterPropertiesSet();
    Configuration cfg = this.getConfiguration();
    cfg.setSharedVariable("shiro", new ShiroTags());//shiro標(biāo)簽
}
}

此時(shí)配置文件中就要引入我們自己的FreeMark的配置

<!--配置freeMarker的模板路徑 -->
<bean class="cn.kiring.crm.shiro.MyCRMFreeMarkerConfig">
    <!-- 配置freemarker的文件編碼 -->
    <property name="defaultEncoding" value="UTF-8" />
    <!-- 配置freemarker尋找模板的路徑(相當(dāng)于前綴) -->
    <property name="templateLoaderPath" value="/WEB-INF/views/" />
</bean>

<!--freemarker視圖解析器 -->
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <!-- 是否在model自動(dòng)把session中的attribute導(dǎo)入進(jìn)去; -->
    <property name="exposeSessionAttributes" value="true" />
    <!-- 配置邏輯視圖自動(dòng)添加的后綴名 -->
    <property name="suffix" value=".ftl" />
    <!-- 配置視圖的輸出HTML的contentType -->
    <property name="contentType" value="text/html;charset=UTF-8" />
</bean>

關(guān)于登錄

  • 在過濾器的配置中配置loginURL就為我們自己的登錄頁面->login.html,而在我們的登錄頁面上的表單的action也是/login.html,那么為什么是這樣欲逃?

通過查看認(rèn)證過濾器中isLoginSubmission()方法就能發(fā)現(xiàn)找蜜,這個(gè)方法內(nèi)部有進(jìn)行判斷這個(gè)請(qǐng)求是什么方式,如果是POST方式稳析,就會(huì)進(jìn)行登錄驗(yàn)證操作洗做,否則就是普通的訪問這個(gè)資源,又因?yàn)槲覀冊(cè)趕hiro.xml中的過濾器的配置上配置了loginUrl屬性值彰居,所以過濾器不會(huì)攔截這個(gè)請(qǐng)求诚纸。

  • 我們的登錄頁面的表單的name一定是"username" 和 "password"

通過查看認(rèn)證過濾器FormAuthenticationFilter里的getUsername()和getPassword()可以發(fā)現(xiàn)shiro底層就是使用request.getParameter("username")和request.getParameter("password")來獲取用戶登錄數(shù)據(jù)并裝進(jìn)token的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市陈惰,隨后出現(xiàn)的幾起案子畦徘,更是在濱河造成了極大的恐慌,老刑警劉巖抬闯,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件井辆,死亡現(xiàn)場離奇詭異,居然都是意外死亡画髓,警方通過查閱死者的電腦和手機(jī)掘剪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門平委,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奈虾,“玉大人,你說我怎么就攤上這事廉赔∪馕ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵蜡塌,是天一觀的道長碉纳。 經(jīng)常有香客問我,道長馏艾,這世上最難降的妖魔是什么劳曹? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任奴愉,我火速辦了婚禮,結(jié)果婚禮上铁孵,老公的妹妹穿的比我還像新娘锭硼。我一直安慰自己,他們只是感情好蜕劝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布檀头。 她就那樣靜靜地躺著,像睡著了一般岖沛。 火紅的嫁衣襯著肌膚如雪暑始。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天婴削,我揣著相機(jī)與錄音廊镜,去河邊找鬼。 笑死馆蠕,一個(gè)胖子當(dāng)著我的面吹牛期升,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播互躬,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼播赁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吼渡?” 一聲冷哼從身側(cè)響起容为,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寺酪,沒想到半個(gè)月后坎背,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寄雀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年得滤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盒犹。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡懂更,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出急膀,到底是詐尸還是另有隱情沮协,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布卓嫂,位于F島的核電站慷暂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晨雳。R本人自食惡果不足惜行瑞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一奸腺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧血久,春花似錦洋机、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至副砍,卻和暖如春衔肢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豁翎。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工角骤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人心剥。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓邦尊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親优烧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝉揍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容