Apache Shiro 是 Java 的一個安全(權限)框架。它能夠很是容易的開發出足夠安全的應用,其不只能夠用在 JavaSE 環境,也能夠用在 JavaEE 環境 。前端
Shiro 能夠完成:認證、受權、加密、會話管理、與Web 集成、緩存 等。下載:http://shiro.apache.org/ 或 https://github.com/apache/shirojava
Shiro目標:Shiro開發團隊所稱的「應用程序安全」的四個基石——身份驗證、受權、會話管理和密碼git
還有額外的功能來支持和加強:github
從外部來看Shiro ,即從應用程序角度的來觀察如何使用 Shiro 完成工做:web
從Shiro內部來看:spring
咱們先導入Shiro的所需jar,咱們先經過構建一個JavaSE應用來把Shiro運行起來 ,能夠參照Shiro源碼包下 (shiro-shiro-root-1.3.2\samples\quickstart)的快速開始數據庫
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.4</version> </dependency> </dependencies>
Shiro須要slf4j來作日誌,因此slf4j不能缺乏apache
這些配置文件直接放在Src下,若是是Maven項目就放在resources瀏覽器
咱們直接使用下載的Demo中quickstart下面的Quickstart.java類便可,它有一個main方法,咱們直接運行看到沒有異常並打印出日誌信息就是運行成功了緩存
咱們通常狀況下都是使用Spring來整合Shiro,在Web環境下對URL進行驗證等工做。其經過一個 ShiroFilter 入口來攔截須要安全控制的URL,而後進行相應的控制
ShiroFilter 相似於如 Strut2/SpringMVC 這種 web 框架的前端控制器,是安全控制的入口點,其負責讀取配置(如ini 配置文件或Spring整合以後的filterChainDefinitions屬性),而後判斷URL是否須要登陸/權限等工做。
上面兩步就不細說了。下面開始配置文件(能夠參照:shiro-root-1.3.2-sourcerelease\shiro-root-1.3.2\samples\spring 配置 web.xml 文件和 Spring 的配置文件)
web.xml:主要配置關於Shiro的Filter
<!-- Filters 配置Shiro過濾器--> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
注意:這個filter-name的值,咱們須要它與Spring整合文件的 org.apache.shiro.spring.web.ShiroFilterFactoryBean 這個bean的id值一致
Spring整合文件,主要的配置有
<!-- 1. 配置 SecurityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="sessionMode" value="native"/> <property name="realm" ref="jdbcRealm"/> </bean> <!-- 2. 配置 CacheManager. (用戶受權信息Cache) 2.1 須要加入 ehcache 的 jar 包及配置文件. --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!-- 3. 配置 Realm 3.1 直接配置實現了 org.apache.shiro.realm.Realm 接口的 bean --> <bean id="jdbcRealm" class="cn.lynu.realms.ShiroRealm"></bean> <!-- 4. 配置 LifecycleBeanPostProcessor. 能夠自定的來調用配置在 Spring IOC 容器中 shiro bean 的生命週期方法. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 5. 啓用 IOC 容器中使用 shiro 的註解. 但必須在配置了 LifecycleBeanPostProcessor 以後纔可使用. --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- 6. 配置 ShiroFilter. 6.1 id 必須和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致. 若不一致, 則會拋出: NoSuchBeanDefinitionException. 由於 Shiro 會來 IOC 容器中查找和 <filter-name> 名字對應的 filter bean. 能夠設置targetBeanName指定Shiro的filter名 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/index.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <!-- 配置哪些頁面須要受保護. 以及訪問這些頁面須要的權限. 1). anon 能夠被匿名訪問 2). authc 必須認證(即登陸)後纔可能訪問的頁面. 3). logout 登出. 4). roles 角色過濾器 --> <property name="filterChainDefinitions"> <value> /login.jsp = anon # everything else requires authentication: /** = authc </value> </property> </bean>
我這裏只配了一個login.jsp能夠在未登陸狀況下訪問,訪問其餘的頁面都會由於未受權而重定向到login.jsp
其格式是:「url=過濾器[參數]」。若是當前請求的 url 匹配 [urls] 部分的某個 url 模式,將會執行其配置的過濾器。部分Shiro內置的過濾器:
過濾器 | 過濾器類 | 描述 | 例子 |
anon | org,apache.shiro.web.filter.authc.AnonymousFilter | 能夠直接訪問 | /admin=anon |
authc | org,apache.shiro.web.filter.authc.FormAuthenticationFilter | 必須認證(登陸)以後才能訪問 | /admin=authc |
logout | org,apache.shiro.web.filter.authc.logoutFilter | 註銷登陸:全部的session都會失效,全部身份都會失去關聯,remember Me Cookie也會刪除 | /logout=logout |
user | org,apache.shiro.web.filter.authc.UserFilter | 表示只要有用戶存在,不管是認證後或rememberMe均可以訪問 | /admin=user |
roles | org,apache.shiro.web.filter.authc.RolesAuthorizcationFilter | 角色過濾器,判斷當前用戶是否爲該角色。參數能夠有多個,多個參數必須加上引號,多個參數之間用逗號分隔 | admin/**=roles["admin,guest"] |
url 模式使用 Ant 風格模式 ,Ant 路徑通配符支持 ?、 * 、 **,注意通配符匹配不包括目錄分隔符「/」:
其實咱們在Spring整合Shiro的配置文件的URL過濾器能夠經過編碼的方式配置,這樣咱們能夠將URL和過濾器配置到數據表中,經過編碼查詢數據庫的方式得到對應的關係,添加進一個 LinkedHashMap。咱們就建一個名爲 FilterChainDefinitionMapBuilder 的類,在這個類中寫一個 buildFilterChainDefinitionMap方法,咱們在這個方法中能夠查詢數據庫獲得URL和過濾器的對應關係,這裏就用模擬數據:
public class FilterChainDefinitionMapBuilder { public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){ LinkedHashMap<String, String> map = new LinkedHashMap<>(); map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); map.put("/user.jsp", "authc,roles[user]"); map.put("/admin.jsp", "authc,roles[admin]"); map.put("/list.jsp", "user"); map.put("/**", "authc"); return map; }
而後修改Spring整合Shiro配置文件的 filterChainDefinitions :
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> </bean> <!-- 配置一個 bean, 該 bean 其實是一個 Map. 經過實例工廠方法的方式 --> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean> <bean id="filterChainDefinitionMapBuilder" class="cn.lynu.factory.FilterChainDefinitionMapBuilder"></bean>
這個 cn.lynu.factory.FilterChainDefinitionMapBuilder 就是咱們寫的名爲 FilterChainDefinitionMapBuilder 的類,buildFilterChainDefinitionMap就是這個類的中寫的方法
使用Shiro能夠對密碼進行加密操做,常見的MD5,SHA1都是支持的。咱們在前端得到密碼以後,Shiro會根據咱們的配置進行對應的加密,並於數據庫中的加密後字符串進行比較,比較成功以後就會放行進入受保護的頁面,比對失敗則會重定向到登陸頁。
咱們先寫個Controller,若是訪問該請求,handler類比Quickstart.java類訪問咱們自定義的realm:
package cn.lynu.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping("/shiro") public class ShiroController { @RequestMapping("/login") public String login(@RequestParam("userName") String userName, @RequestParam("password") String password) { Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { //把用戶名和密碼封裝爲UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken(userName, password); //Remember Me 操做 token.setRememberMe(true); try { System.out.println("token:"+token.hashCode()); //執行登陸 currentUser.login(token); } //AuthenticationException是全部認證失敗的父類 catch (AuthenticationException ae) { System.err.println("登陸失敗:"+ae); } } return "redirect:/index.jsp"; } }
Subject的login方法就能夠訪問咱們配置的realm,咱們還須要在Spring的Shiro整合文件中配置這個URL爲anon(未登陸也可訪問)
再來看看咱們配置的realm,咱們先使用明文進行測試,這裏的自定義realm繼承於AuthenticatingRealm,AuthenticatingRealm實現了realm接口:
public class ShiroRealm extends AuthenticatingRealm{ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("doGetAuthenticationInfo1:"+token.hashCode()); //1.將AuthenticationToken強轉爲UsernamePasswordToken UsernamePasswordToken uspaToken=(UsernamePasswordToken)token; //2.得到用戶名 String userName=uspaToken.getUsername(); //3.查詢數據庫得到真實的用戶名或密碼(這裏模擬) //3.1若用戶不存在,拋出UnknownAccountException if("unknown".equals(userName)) { throw new UnknownAccountException("用戶不存在"); } //3.2根據用戶信息拋出其餘信息(這裏使用被鎖定,拋出LockedAccountException) if("lock".equals(userName)){ throw new LockedAccountException("用戶被鎖定"); } //4.根據用戶信息構建AuthenticationInfo,咱們經常使用其子類: //1).principal 用戶實體信息 能夠是userName,也能夠是數據表對應的實體類信息 Object principal=userName; //2).credentials 密碼 Object credentials="123"; //3).realmName 使用當前realName便可 String realmName=this.getName(); SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, realmName); return info; }
這裏用戶名和密碼的在真實環境中應該從數據庫中查到,爲了不麻煩這裏只要用戶名不爲unknown或lock,密碼爲123就能夠登陸成功了。
若是用戶名爲unknown就拋一個 UnknownAccountException (不存在帳戶的異常);若是用戶名爲lock就拋一個LockedAccountException(帳戶鎖定異常),這倆個異常類都是AuthenticationException的子類,因此能夠拋出,AuthenticationException還有其餘的子類,咱們能夠根據用戶信息來拋異常的方式終止登陸。
登陸成功以後咱們就會根據在Controller中配置的重定向到index.jsp,在以前未登陸的狀況下,咱們是不能訪問這個頁面的
可是這裏有個問題:咱們知道了Shiro內部使用緩存(ehCache)來保存驗證,認證信息之類的,因此就出現了登陸成功以後再次回到登陸頁,再次登陸不管輸入什麼用戶名和密碼都會登陸成功,由於登陸成功的信息已經被緩存,這的時候就須要咱們進行登出操做:
<a href="shiro/logout">登出</a>
這裏簡單的使用一個超連接訪問URL->shiro/logout,咱們只須要在Spring的Shiro整合文件中配置這個URL爲logout便可
只須要在這裏配置一下,咱們點擊登出超連接以後就會重定向到login.jsp要求再次登陸
下面來到重頭戲:加密。這裏以MD5加密爲例,首先咱們須要在Spring的Shiro整合文件中配置自定義realm指定所需加密方式和加密次數:
加密次數與複雜度成正比,這樣配置以後前端傳過來的密碼就會被Shiro以MD5進行1024次加密
由於存在於加密後的字符串進行比較,因此咱們必須先知道加密後的結果,真實的使用固然是從數據庫中查出來,咱們這裏簡單點,使用Shiro提供的SimpleHash類來得到加密後的字符串。
在加密以前,咱們再來複習密碼學中的加鹽概念:相同的密碼通過加鹽加密以後就會獲得不一樣的加密字符串,這樣咱們將不一樣的加密字符串保存在數據庫中,也不會看得出這是相同密碼加密以後的結果,提升安全性。對於鹽值的要求:惟一不重複,對於每一個用戶使用其惟一的屬性,例如用戶名之類的做爲鹽值,這樣不一樣用戶即便密碼相同,加鹽加密以後也會獲得不一樣的加密字符串。
public static void main(String[] args) { //加密方式 String algorithmName="MD5"; //密碼 Object credentials="123"; //鹽值(通常將一個惟一值做爲鹽值) Object salt=ByteSource.Util.bytes("admin"); //加密次數 int hashIterations=1024; SimpleHash simpleHash = new SimpleHash(algorithmName, credentials, salt, hashIterations); System.out.println(simpleHash); }
simpleHash就能夠獲得加密以後的字符串,這裏鹽值使用了Shiro提供的 ByteSource.Util.bytes 方法進行得到鹽值,這裏使用admin來得到鹽值,由於一會咱們直接使用用戶名爲admin(將至關於在使用用戶名這個惟一值來生成鹽值),密碼爲123進行登陸
修改咱們自定義Realm(Shirorealm)的doGetAuthenticationInfo方法:
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("doGetAuthenticationInfo1:"+token.hashCode()); //1.將AuthenticationToken強轉爲UsernamePasswordToken UsernamePasswordToken uspaToken=(UsernamePasswordToken)token; //2.得到用戶名 String userName=uspaToken.getUsername(); //3.查詢數據庫得到真實的用戶名或密碼(這裏模擬) //3.1若用戶不存在,拋出UnknownAccountException if("unknown".equals(userName)) { throw new UnknownAccountException("用戶不存在"); } //3.2根據用戶信息拋出其餘信息(這裏使用被鎖定,拋出LockedAccountException) if("lock".equals(userName)){ throw new LockedAccountException("用戶被鎖定"); } //4.根據用戶信息構建AuthenticationInfo,咱們經常使用其子類: //1).principal 用戶實體信息 能夠是userName,也能夠是數據表對應的實體類信息 Object principal=userName; //2).credentials 密碼 Object credentials=null; //這裏使用加鹽密碼 if("admin".equals(userName)) { credentials="c41d7c66e1b8404545aa3a0ece2006ac"; } //3).realmName 使用當前realName便可 String realmName=this.getName(); //4).鹽值(原始密碼一致,可是經過加鹽加密以後的字符串會不同,提升安全性) ByteSource credentialsSalt=ByteSource.Util.bytes(userName); SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; }
Ok,如今只有用戶名爲admin,密碼爲123的才能夠登陸成功
若是存在多個Realm,咱們應該如何配置呢?
1.這個時候Spring的Shiro整合文件就須要先配置多個Realm,這裏咱們配兩個:
<bean id="jdbcRealm" class="cn.lynu.realms.ShiroRealm"> <!-- 設置加密方式和加密次數 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean> <!--第二個realm --> <bean id="secondRealm" class="cn.lynu.realms.ShiroRealm2"> <!-- 設置加密方式和加密次數 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA1"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean>
一個id爲jdbcRealm,另外一個id爲secondRealm
2.修改SecurityManager,以前使用一個realm,因此就直接將realm配置在SecurityManager裏面了,這裏咱們使用realms屬性替代:
<!-- 1. 配置 SecurityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="sessionMode" value="native"/> <!-- 配置單個realm <property name="realm" ref="jdbcRealm"/> --> <!--配置多個realm --> <property name="authenticator" ref="authenticator"></property> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="secondRealm"/> </list> </property> </property> </bean>
注意看這個 realms 值使用的是list,因此到驗證的時候順序就根據在這裏配置的realm前後順序進行驗證
配置後以後,咱們再來看看這個ShiroRealm2如何寫的(其實就是根據ShiroRealm改過來的,改成使用SHA1加密方式)
package cn.lynu.realms; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.util.ByteSource; public class ShiroRealm2 extends AuthenticatingRealm{ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("doGetAuthenticationInfo2:"+token.hashCode()); //1.將AuthenticationToken強轉爲UsernamePasswordToken UsernamePasswordToken uspaToken=(UsernamePasswordToken)token; //2.得到用戶名 String userName=uspaToken.getUsername(); //3.查詢數據庫得到真實的用戶名或密碼(這裏模擬) //3.1若用戶不存在,拋出UnknownAccountException if("unknown".equals(userName)) { throw new UnknownAccountException("用戶不存在"); } //3.2根據用戶信息拋出其餘信息(這裏使用被鎖定,拋出LockedAccountException) if("lock".equals(userName)){ throw new LockedAccountException("用戶被鎖定"); } //4.根據用戶信息構建AuthenticationInfo,咱們經常使用其子類: //1).principal 用戶實體信息 能夠是userName,也能夠是數據表對應的實體類信息 Object principal=userName; //2).credentials 密碼 Object credentials=null; //這裏使用加鹽密碼 if("admin".equals(userName)) { credentials="49d9fbf007fd95343492e607aa34455eeb062b26"; } //3).realmName 使用當前realName便可 String realmName=this.getName(); //4).鹽值(原始密碼一致,可是經過加鹽加密以後的字符串會不同,提升安全性) ByteSource credentialsSalt=ByteSource.Util.bytes(userName); SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; } public static void main(String[] args) { //加密方式 String algorithmName="SHA1"; //密碼 Object credentials="123"; //鹽值(通常將一個惟一值做爲鹽值) Object salt=ByteSource.Util.bytes("admin"); //加密次數 int hashIterations=1024; SimpleHash simpleHash = new SimpleHash(algorithmName, credentials, salt, hashIterations); System.out.println(simpleHash); } }
爲了區分,在這兩個Realm的開始都打印一句: System.out.println("doGetAuthenticationInfo1:"+token.hashCode()); System.out.println("doGetAuthenticationInfo2:"+token.hashCode()); 用於區分不一樣的realm
運行以後會發現先打印的是doGetAuthenticationInfo1 後打印doGetAuthenticationInfo2,這與咱們在realms屬性中配置的順序有關
多個Realm運行的前後順序咱們已經知道了與realms屬性有關,可是多個Realm用於驗證,以哪一個爲主呢?
<!--配置多realm運行策略 --> <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> </property> </bean>
並加入進 securityManager 中管理
<property name="authenticator" ref="authenticator"></property>
咱們其實在ModularRealmAuthenticator中配置了authenticationStrategy(驗證策略)爲AtLeastOneSuccessfulStrategy(只要有一個Realm驗證成功便可。這其實也是默認的驗證策略)。驗證策略還有:
這些驗證策略都是AuthenticationStrategy 接口的實現。咱們能夠修改驗證策略來知足咱們的需求
有的時候咱們須要給用戶分配角色,用於區分不一樣角色的用戶均可以作什麼操做。這個時候使用的仍是自定義的Realm,以前咱們在驗證的時候是將自定義的realm繼承AuthenticatingRealm類(實現了realm接口),而咱們受權是將自定義的realm繼承AuthorizingRealm,AuthorizingRealm類是AuthenticatingRealm類的子類。驗證使用的是重寫AuthenticatingRealm的doGetAuthenticationInfo方法,而AuthorizingRealm類由於是子類也有這個方法,咱們也可使用這個方法完成驗證,而受權的方法是doGetAuthorizationInfo方法,咱們須要重寫它。這兩個類和方法都比較接近,注意區分。
public class ShiroRealm extends AuthorizingRealm{ //用於驗證的方法 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("doGetAuthenticationInfo1:"+token.hashCode()); //1.將AuthenticationToken強轉爲UsernamePasswordToken UsernamePasswordToken uspaToken=(UsernamePasswordToken)token; //2.得到用戶名 String userName=uspaToken.getUsername(); //3.查詢數據庫得到真實的用戶名或密碼(這裏模擬) //3.1若用戶不存在,拋出UnknownAccountException if("unknown".equals(userName)) { throw new UnknownAccountException("用戶不存在"); } //3.2根據用戶信息拋出其餘信息(這裏使用被鎖定,拋出LockedAccountException) if("lock".equals(userName)){ throw new LockedAccountException("用戶被鎖定"); } //4.根據用戶信息構建AuthenticationInfo,咱們經常使用其子類: //1).principal 用戶實體信息 能夠是userName,也能夠是數據表對應的實體類信息 Object principal=userName; //2).credentials 密碼 Object credentials=null; //這裏使用加鹽密碼 if("admin".equals(userName)) { credentials="c41d7c66e1b8404545aa3a0ece2006ac"; } if("user".equals(userName)) { credentials="2bbffae8c52dd2532dfe629cecfb2c85"; } //3).realmName 使用當前realName便可 String realmName=this.getName(); //4).鹽值(原始密碼一致,可是經過加鹽加密以後的字符串會不同,提升安全性) ByteSource credentialsSalt=ByteSource.Util.bytes(userName); SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; } //用於受權的方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //1. 從 PrincipalCollection 中來獲取登陸用戶的信息 Object principal = principals.getPrimaryPrincipal(); //2. 利用登陸的用戶的信息來用戶當前用戶的角色或權限(可能須要查詢數據庫) Set<String> roles = new HashSet<>(); roles.add("user"); if("admin".equals(principal)){ roles.add("admin"); } //3. 建立 SimpleAuthorizationInfo, 並設置其 roles 屬性. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); //4. 返回 SimpleAuthorizationInfo 對象. return info; } }
doGetAuthorizationInfo方法中默認給全部用戶賦予的是user的角色,而若是用戶名爲admin的用戶還會額外賦予admin角色。別忘了在Spring的Shiro的配置文件中配置這個自定義的realm
user和admin角色均可以作什麼呢?
咱們在Spring與Shiro的整合配置文件中
/user.jsp=authc,roles[user]
/admin.jsp=authc,roles[admin]
user.jsp只有在認證和擁有user角色的狀況下才能夠訪問,而admin.jsp在認證和擁有admin角色的狀況下才能夠訪問
如今的user.jsp和admin.jsp只有在登陸以後並擁有對應權限的用戶在能夠訪問。
Shiro提供了一套標籤供咱們在JSP頁面進行權限控制,首先在頁面導入Shiro標籤庫
<%-- 使用Shiro標籤 --%> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
guest標籤:用戶沒有身份驗證時顯示對應信息,及遊客能夠看到的信息
<shiro:guest> 歡迎遊客訪問,<a href="login.jsp">登陸</a> </shiro:guest>
user標籤:用戶已經認證/記住我登陸以後顯示的信息
<shiro:user> 歡迎[<shiro:prinicpal/>]登陸,<a href="logout">退出</a> </shiro:user>
authenticated標籤:用戶已經身份驗證經過,即經過Subject.logon登陸成功,而不是經過記住我登陸,顯示的信息
<shiro:authenticated> 用戶[<shiro:principal/>]已經身份驗證經過 </shiro:authenticated>
notAuthenticated標籤:用戶未進行身份認證,即沒經過Subject.login進行登陸,記住我自動登陸也屬於爲未通過身份驗證。
<shiro:notAuthenticated> 未身份驗證(包括記住我) <shiro:notAuthenticated>
principal標籤:顯示用戶身份信息.即調用了Subject.getPrincipal()得到PrimaryPrincipal。
<shiro:principaol/>
hasRole標籤:若是當前Subject有角色將顯示內容
<shiro:hasRole name="admin"> 用戶[<shiro:principal/>]擁有admin角色<be> </shiro:hasRole>
hasAnyRoles標籤:若是當前Subject有任意一個角色(或的關係)將顯示內容
<shiro:hasAnyRoles name="admin,user"> 用戶[<shiro:principal/>]擁有角色admin或user<br> </shiro:hasAnyRoles>
lacksRole標籤:若是當前subject沒有對應角色將顯示的內容
<shiro:lacksRole name="admin"> 用戶[<shiro:principal/>]沒有角黑色admin<br> </shiro:lacksRole>
hasPermission標籤: 若是當前Subject有權限則顯示內容
<shiro:hasPermission name="user:create"> 用戶[<shiro:principal/>]擁有權限user:create<br> </shiro:hasPermission>
lacksPermission標籤:若是當前Subject沒有權限將顯示內容
<shiro:lacksPermission name="user:create"> 用戶[<shiro:principal/>]沒有權限user:create </shiro:lacksPermission>
@RequiresAuthentication:表示Subject已經經過login進行了身份認證;即Subject.isAuthenticated()返回true
@RequiresUser:表示當前Subject已經經過身份驗證或記住我登陸
@RequiresGuest:表示是當前Subject沒有身份認證或記住我登陸過,便是遊客身份
@RequiresRoles(values={"admin","user"},logical=Logical.OR):表示當前Subject須要角色admin或user
我在Service層配置一個方法只有擁有admin在可運行:
@RequiresRoles(value={"admin"}) public void testMethod(){ System.out.println("Now Date:"+new Date()); }
這樣只有擁有admin角色的用戶調用這個方法纔會打印日期,其餘角色的用戶調用這個方法不會成功,會拋一個異常:
咱們能夠根據這個異常作一個異常處理:只要出現這個異常就轉到一個頁面,提示一些東西
可是我在測試的時候遇到一些問題:若是使用註解標註Service那麼Shiro就失效了,只有用XML中聲明式的配置Service才能夠?
Shiro提供了完整的會話管理,它不依賴於底層容器(如web容器tomcat),無論JavaSE仍是JavaEE均可以使用,提供了會話管理,會話監聽,失效/過時支持等等
會話相關的API
這裏咱們完成一個操做:在Controller層往Session中放入一個名爲userName的lz值,並在Service層取出這個userName值,不要將HttpSession傳入給Service層避免代碼侵入,使用Shiro的Session得到這個值
Controller:
@RequestMapping("/testShiroAnnotation") public String testShiroAnnotation(HttpSession session){ session.setAttribute("userName", "lz"); shiroService.testMethod(); return "redirect:/list.jsp"; }
Service:
public void testMethod(){ Session session = SecurityUtils.getSubject().getSession(); Object val = session.getAttribute("userName"); System.out.println("Service SessionVal: " + val); }
Shiro 提供了記住我(RememberMe)的功能,好比訪問如淘寶 等一些網站時,關閉了瀏覽器,下次再打開時仍是能記住你是誰, 下次訪問時無需再登陸便可訪問,基本流程以下:
咱們以前其實一直使用了rememberMe功能:
//把用戶名和密碼封裝爲UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken(userName, password); //Remember Me 操做 token.setRememberMe(true);
咱們能夠在前端加一個checkbox用於判斷是否須要rememberMe,若是勾選,這個設置setRememberMe 爲true
由於rememberMe底層使用的是Cookies,咱們能夠在Spring整合Shiro的配置文件的securityManager配置rememberMeManager.cookie.maxAge
<!--設置remember Me 的Cookie時間 單位秒 --> <property name="rememberMeManager.cookie.maxAge" value="1800"></property>