Apache Shiro安全(權限框架)學習筆記二

 課程目標

經過學習本課程掌握權限管理的設計思想及方法,使用Shiro框架完成權限管理功能開發。javascript

 

一、  理解基於資源的權限管理方法。java

二、  掌握權限管理的數據模型。mysql

三、  掌握不使用shiro開發基於url的權限管理方法。jquery

四、  掌握Shiro進行用戶認證的經常使用方法。web

五、  掌握Shiro進行受權的經常使用方法。算法

六、  掌握Shiro整合企業應用開發的方法。spring

 

 權限管理

2.1    什麼是權限管理

         基本上涉及到用戶參與的系統都要進行權限管理,權限管理屬於系統安全的範疇,權限管理實現對用戶訪問系統的控制,按照安全規則或者安全策略控制用戶能夠訪問並且只能訪問本身被受權的資源。sql

         權限管理包括用戶身份認證和受權兩部分,簡稱認證受權。對於須要訪問控制的資源用戶首先通過身份認證,認證經過後用戶具備該資源的訪問權限方可訪問。數據庫

        

2.2    用戶身份認證

2.2.1  概念

         身份認證,就是判斷一個用戶是否爲合法用戶的處理過程。最經常使用的簡單身份認證方式是系統經過覈對用戶輸入的用戶名和口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。對於採用指紋等系統,則出示指紋;對於硬件Key等刷卡系統,則須要刷卡。apache

 

 

2.2.2  用戶名密碼身份認證流程

 

 

 

 

2.2.3  關鍵對象

         上邊的流程圖中須要理解如下關鍵對象:

 

Subject:主體

         訪問系統的用戶,主體能夠是用戶、程序等,進行認證的都稱爲主體;

 

Principal:身份信息

         是主體(subject)進行身份認證的標識,標識必須具備惟一性,如用戶名、手機號、郵箱地址等,一個主體能夠有多個身份,可是必須有一個主身份(Primary Principal)。

 

credential:憑證信息

         是隻有主體本身知道的安全信息,如密碼、證書等。

 

 

2.3    受權

2.3.1  概念

         受權,即訪問控制,控制誰能訪問哪些資源。主體進行身份認證後須要分配權限方可訪問系統的資源,對於某些資源沒有權限是沒法訪問的。

 

2.3.2  受權流程

 下圖中橙色爲受權流程。

 

 

 

2.3.3  關鍵對象

    受權可簡單理解爲who對what(which)進行How操做:

n  Who,即主體(Subject),主體須要訪問系統中的資源。

n  What,即資源(Resource),如系統菜單、頁面、按鈕、類方法、系統商品信息等。資源包括資源類型和資源實例,好比商品信息爲資源類型,類型爲t01的商品爲資源實例,編號爲001的商品信息也屬於資源實例。

n  How,權限/許可(Permission),規定了主體對資源的操做許可,權限離開資源沒有意義,如用戶查詢權限、用戶添加權限、某個類方法的調用權限、編號爲001用戶的修改權限等,經過權限可知主體對哪些資源都有哪些操做許可。

權限分爲粗顆粒和細顆粒,粗顆粒權限是指對資源類型的權限,細顆粒權限是對資源實例的權限。

 

主體、資源、權限關係以下圖:

 

 

 

 

2.3.4  權限模型

對上節中的主體、資源、權限經過數據模型表示。

 

主體(帳號、密碼)

資源(資源名稱、訪問地址)

權限(權限名稱、資源id)

角色(角色名稱)

角色和權限關係(角色id、權限id)

主體和角色關係(主體id、角色id)

 

 

以下圖:

 

 

一般企業開發中將資源和權限表合併爲一張權限表,以下:

資源(資源名稱、訪問地址)

權限(權限名稱、資源id)

合併爲:

權限(權限名稱、資源名稱、資源訪問地址)

 

 

 

上圖常被稱爲權限管理的通用模型,不過企業在開發中根據系統自身的特色還會對上圖進行修改,可是用戶、角色、權限、用戶角色關係、角色權限關係是須要去理解的。

 

 

2.3.5  權限分配

         對主體分配權限,主體只容許在權限範圍內對資源進行操做,好比:對u01用戶分配商品修改權限,u01用戶只能對商品進行修改。

         權限分配的數據一般須要持久化,根據上邊的數據模型建立表並將用戶的權限信息存儲在數據庫中。

 

 

2.3.6  權限控制

         用戶擁有了權限便可操做權限範圍內的資源,系統不知道主體是否具備訪問權限須要對用戶的訪問進行控制。

 

 

2.3.6.1 基於角色的訪問控制

         RBAC基於角色的訪問控制(Role-Based Access Control)是以角色爲中心進行訪問控制,好比:主體的角色爲總經理能夠查詢企業運營報表,查詢員工工資信息等,訪問控制流程以下:

 

 

 

 

上圖中的判斷邏輯代碼能夠理解爲:

if(主體.hasRole("總經理角色id")){

         查詢工資

}

 

 

缺點:以角色進行訪問控制粒度較粗,若是上圖中查詢工資所須要的角色變化爲總經理和部門經理,此時就須要修改判斷邏輯爲「判斷主體的角色是不是總經理或部門經理」,系統可擴展性差。

修改代碼以下:

if(主體.hasRole("總經理角色id") ||  主體.hasRole("部門經理角色id")){

         查詢工資

}

 

 

2.3.6.2 基於資源的訪問控制

         RBAC基於資源的訪問控制(Resource-Based Access Control)是以資源爲中心進行訪問控制,好比:主體必須具備查詢工資權限才能夠查詢員工工資信息等,訪問控制流程以下:

 

 

上圖中的判斷邏輯代碼能夠理解爲:

if(主體.hasPermission("查詢工資權限標識")){

         查詢工資

}

 

優勢:系統設計時定義好查詢工資的權限標識,即便查詢工資所須要的角色變化爲總經理和部門經理也只須要將「查詢工資信息權限」添加到「部門經理角色」的權限列表中,判斷邏輯不用修改,系統可擴展性強。

 

權限管理解決方案

3.1    粗顆粒度和細顆粒度

3.1.1  什麼是粗顆粒度和細顆粒度

         對資源類型的管理稱爲粗顆粒度權限管理,即只控制到菜單、按鈕、方法,粗粒度的例子好比:用戶具備用戶管理的權限,具備導出訂單明細的權限。對資源實例的控制稱爲細顆粒度權限管理,即控制到數據級別的權限,好比:用戶只容許修改本部門的員工信息,用戶只容許導出本身建立的訂單明細。

 

3.1.2  如何實現粗顆粒度和細顆粒度

         對於粗顆粒度的權限管理能夠很容易作系統架構級別的功能,即系統功能操做使用統一的粗顆粒度的權限管理。

         對於細顆粒度的權限管理不建議作成系統架構級別的功能,由於對數據級別的控制是系統的業務需求,隨着業務需求的變動業務功能變化的可能性很大,建議對數據級別的權限控制在業務層個性化開發,好比:用戶只容許修改本身建立的商品信息能夠在service接口添加校驗實現,service接口須要傳入當前操做人的標識,與商品信息建立人標識對比,不一致則不容許修改商品信息。

 

3.2    基於url攔截

         基於url攔截是企業中經常使用的權限管理方法,實現思路是:將系統操做的每一個url配置在權限表中,將權限對應到角色,將角色分配給用戶,用戶訪問系統功能經過Filter進行過慮,過慮器獲取到用戶訪問的url,只要訪問的url是用戶分配角色中的url則放行繼續訪問。

         以下圖:

 

 

 

 

                  

3.3    使用權限管理框架

         對於權限管理基本上每一個系統都有,使用權限管理框架完成權限管理功能的開發能夠節省系統開發時間,而且權限管理框架提供了完善的認證和受權功能有利於系統擴展維護,可是學習權限管理框架是須要成本的,因此選擇一款簡單高效的權限管理框架顯得很是重要。

 

基於url攔截實現

4.1    環境準備

jdk:1.7.0_72

web容器:tomcat7

系統框架:springmvc3.2.0+mybatis3.2.7(詳細參考springmvc教案)

前臺UI:jquery easyUI1.2.2

 

4.2    數據庫

建立mysql5.1數據庫

建立用戶表、角色表、權限表、角色權限關係表、用戶角色關係表。

導入腳本,先導入shiro_sql_talbe.sql再導入shiro-sql_table_data.sql

 

 

4.3    activeUser用戶身份類

用戶登錄成功記錄activeUser信息並將activeUser存入session。

 

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;// 權限

 

 

4.4    anonymousURL.properties

anonymousURL.properties公開訪問地址,無需身份認證便可訪問。

4.5    commonURL.properties

commonURL.properties公共訪問地址,身份認證經過無需分配權限便可訪問。

 

 

4.6    用戶身份認證攔截器

         使用springmvc攔截器對用戶身份認證進行攔截,若是用戶沒有登錄則跳轉到登錄頁面,本功能也可使用filter實現 。

 

public class LoginInterceptor implements HandlerInterceptor {

 

    // 在進入controller方法以前執行

    // 使用場景:好比身份認證校驗攔截,用戶權限攔截,若是攔截不放行,controller方法再也不執行

    @Override

    public boolean preHandle(HttpServletRequest request,

           HttpServletResponse response, Object handler) throws Exception {

 

       // 校驗用戶訪問是不是公開資源地址(無需認證便可訪問)

       List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");

 

       // 用戶訪問的url

       String url = request.getRequestURI();

       for (String open_url : open_urls) {

           if (url.indexOf(open_url) >= 0) {

              // 若是訪問的是公開 地址則放行

              return true;

           }

       }

 

       // 校驗用戶身份是否定證經過

       HttpSession session = request.getSession();

       ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");

       if (activeUser != null) {

           // 用戶已經登錄認證,放行

           return true;

       }

       // 跳轉到登錄頁面

       request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,

              response);

       return false;

    }

 

 

4.7    用戶受權攔截器

         使用springmvc攔截器對用戶訪問url進行攔截,若是用戶訪問的url沒有分配權限則跳轉到無權操做提示頁面(refuse.jsp),本功能也可使用filter實現。

        

public class PermissionInterceptor implements HandlerInterceptor {

 

    // 在進入controller方法以前執行

    // 使用場景:好比身份認證校驗攔截,用戶權限攔截,若是攔截不放行,controller方法再也不執行

    // 進入action方法前要執行

    @Override

    public boolean preHandle(HttpServletRequest request,

           HttpServletResponse response, Object handler) throws Exception {

       // TODO Auto-generated method stub

       // 用戶訪問地址:

       String url = request.getRequestURI();

 

       // 校驗用戶訪問是不是公開資源地址(無需認證便可訪問)

       List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");

       // 用戶訪問的url

       for (String open_url : open_urls) {

           if (url.indexOf(open_url) >= 0) {

              // 若是訪問的是公開 地址則放行

              return true;

           }

       }

       //從 session獲取用戶公共訪問地址(認證經過無需分配權限便可訪問)

       List<String> common_urls = ResourcesUtil.gekeyList("commonURL");

       // 用戶訪問的url

       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中權限url

       // 獲取用戶操做權限

       List<SysPermission> permission_list = activeUser.getPermissions();

       // 校驗用戶訪問地址是否在用戶權限範圍內

       for (SysPermission sysPermission : permission_list) {

           String permission_url = sysPermission.getUrl();

           if (url.contains(permission_url)) {

              return true;

           }

       }

 

       // 跳轉到頁面

       request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(

              request, response);

        return false;

    }

 

 

4.8    用戶登錄

用戶輸入用戶帳號和密碼登錄,登錄成功將用戶的身份信息(用戶帳號、密碼、權限菜單、權限url等)記入activeUser類,並寫入session。

 

4.8.1  controller

//用戶登錄提交

    @RequestMapping("/loginsubmit")

    public String loginsubmit(HttpSession session,String usercode,String password,String randomcode) throws Exception{

 

       //校驗驗證碼

       //從session獲取正確的驗證碼

       String validateCode = (String)session.getAttribute("validateCode");

       if(!randomcode.equals(validateCode)){

           //拋出異常:驗證碼錯誤

           throw new CustomException("驗證碼 錯誤 !");

       }

       //用戶身份認證

       ActiveUser activeUser = sysService.authenticat(usercode, password);

      

       //記錄session

       session.setAttribute("activeUser", activeUser);

      

       return "redirect:first.action";

    }

 

4.8.2  service接口

 

/**

     *

     * <p>

     * Title: authenticat

     * </p>

     * <p>

     * Description:用戶認證

     * </p>

     *

     * @param usercode

     *            用戶帳號

     * @param password

     *            用戶密碼

     * @return ActiveUser 用戶身份信息

     * @throws Exception

     */

    public ActiveUser authenticat(String usercode, String password)

           throws Exception;

 

    // 根據帳號查詢用戶

    public SysUser findSysuserByUsercode(String usercode) throws Exception;

 

    // 根據用戶id獲取權限

    public List<SysPermission> findSysPermissionList(String userid)

           throws Exception;

 

    // 根據用戶id獲取菜單

    public List<SysPermission> findMenuList(String userid) throws Exception;

 

 

 

shiro介紹

5.1    什麼是shiro

Shiro是apache旗下一個開源框架,它將軟件系統的安全認證相關的功能抽取出來,實現用戶身份認證,權限受權、加密、會話管理等功能,組成了一個通用的安全認證框架。

5.2    爲何要學shiro

         既然shiro將安全認證相關的功能抽取出來組成一個框架,使用shiro就能夠很是快速的完成認證、受權等功能的開發,下降系統成本。

         shiro使用普遍,shiro能夠運行在web應用,非web應用,集羣分佈式應用中愈來愈多的用戶開始使用shiro。

         java領域中spring security(原名Acegi)也是一個開源的權限管理框架,可是spring security依賴spring運行,而shiro就相對獨立,最主要是由於shiro使用簡單、靈活,因此如今愈來愈多的用戶選擇shiro。

 

5.3    Shiro架構

 

 

 

 

 

5.3.1  Subject

         Subject即主體,外部應用與subject進行交互,subject記錄了當前操做用戶,將用戶的概念理解爲當前操做的主體,多是一個經過瀏覽器請求的用戶,也多是一個運行的程序。         Subject在shiro中是一個接口,接口中定義了不少認證授相關的方法,外部程序經過subject進行認證授,而subject是經過SecurityManager安全管理器進行認證受權

 

5.3.2  SecurityManager

         SecurityManager即安全管理器,對所有的subject進行安全管理,它是shiro的核心,負責對全部的subject進行安全管理。經過SecurityManager能夠完成subject的認證、受權等,實質上SecurityManager是經過Authenticator進行認證,經過Authorizer進行受權,經過SessionManager進行會話管理等。

         SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。

 

5.3.3  Authenticator

         Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,經過ModularRealmAuthenticator基本上能夠知足大多數需求,也能夠自定義認證器。

5.3.4  Authorizer

         Authorizer即受權器,用戶經過認證器認證經過,在訪問功能時須要經過受權器判斷用戶是否有此功能的操做權限。

 

5.3.5  realm

         Realm即領域,至關於datasource數據源,securityManager進行安全認證須要經過Realm獲取用戶權限數據,好比:若是用戶身份數據在數據庫那麼realm就須要從數據庫獲取用戶身份信息。

         注意:不要把realm理解成只是從數據源取數據,在realm中還有認證受權校驗的相關的代碼。

 

5.3.6  sessionManager

sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,因此shiro可使用在非web應用上,也能夠將分佈式應用的會話集中在一點管理,此特性可以使它實現單點登陸。

5.3.7  SessionDAO

SessionDAO即會話dao,是對session會話操做的一套接口,好比要將session存儲到數據庫,能夠經過jdbc將會話存儲到數據庫。

5.3.8  CacheManager

CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣能夠提升性能。

5.3.9  Cryptography

         Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。好比提供經常使用的散列、加/解密等功能。

 

5.4    shiro的jar包

         與其它java開源框架相似,將shiro的jar包加入項目就可使用shiro提供的功能了。shiro-core是核心包必須選用,還提供了與web整合的shiro-web、與spring整合的shiro-spring、與任務調度quartz整合的shiro-quartz等,下邊是shiro各jar包的maven座標。

 

   <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>
pom.xml

 

也能夠經過引入shiro-all包括shiro全部的包:

   <dependency>

         <groupId>org.apache.shiro</groupId>

         <artifactId>shiro-all</artifactId>

         <version>1.2.3</version>

      </dependency>

 

 

參考lib目錄 :

 

 

 

 

shiro認證

6.1    認證流程

 

 

 

6.2    入門程序(用戶登錄和退出)

6.2.1  建立java工程

jdk版本:1.7.0_72

eclipse:elipse-indigo

 

6.2.2  加入shiro-core的Jar包及依賴包

 

 

 

6.2.3  log4j.properties日誌配置文件

log4j.rootLogger=debug, stdout

 

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

 

 

6.2.4  shiro.ini

經過Shiro.ini配置文件初始化SecurityManager環境。

 

配置 eclipse支持ini文件編輯:

 

 

在eclipse配置後,在classpath建立shiro.ini配置文件,爲了方便測試將用戶名和密碼配置的shiro.ini配置文件中:

 

[users]

zhang=123

lisi=123

 

 

6.2.5  認證代碼

// 用戶登錄、用戶退出

    @Test

    public void testLoginLogout() {

 

       // 構建SecurityManager工廠,IniSecurityManagerFactory能夠從ini文件中初始化SecurityManager環境

       Factory<SecurityManager> factory = new IniSecurityManagerFactory(

              "classpath:shiro.ini");

 

       // 經過工廠建立SecurityManager

       SecurityManager securityManager = factory.getInstance();

      

       // 將securityManager設置到運行環境中

       SecurityUtils.setSecurityManager(securityManager);

 

       // 建立一個Subject實例,該實例認證要使用上邊建立的securityManager進行

       Subject subject = SecurityUtils.getSubject();

 

       // 建立token令牌,記錄用戶認證的身份和憑證即帳號和密碼

       UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

 

       try {

           // 用戶登錄

           subject.login(token);

       } catch (AuthenticationException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

 

       // 用戶認證狀態

 

       Boolean isAuthenticated = subject.isAuthenticated();

 

       System.out.println("用戶認證狀態:" + isAuthenticated);

 

       // 用戶退出

 

       subject.logout();

 

       isAuthenticated = subject.isAuthenticated();

 

       System.out.println("用戶認證狀態:" + isAuthenticated);

 

    }

 

 
View Code

 

6.2.6  認證執行流程

 

一、  建立token令牌,token中有用戶提交的認證信息即帳號和密碼

二、  執行subject.login(token),最終由securityManager經過Authenticator進行認證

三、  Authenticator的實現ModularRealmAuthenticator調用realm從ini配置文件取用戶真實的帳號和密碼,這裏使用的是IniRealm(shiro自帶)

四、  IniRealm先根據token中的帳號去ini中找該帳號,若是找不到則給ModularRealmAuthenticator返回null,若是找到則匹配密碼,匹配密碼成功則認證經過。

 

6.2.7  常見的異常

n   UnknownAccountException

帳號不存在異常以下:

org.apache.shiro.authc.UnknownAccountException: No account found for user。。。。

 

 

n   IncorrectCredentialsException

當輸入密碼錯誤會拋此異常,以下:

org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.

 

 

更多以下:

DisabledAccountException(賬號被禁用)

LockedAccountException(賬號被鎖定)

ExcessiveAttemptsException(登陸失敗次數過多)

ExpiredCredentialsException(憑證過時)等

 

 

 

6.3    自定義Realm

         上邊的程序使用的是Shiro自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分狀況下須要從系統的數據庫中讀取用戶信息,因此須要自定義realm。

 

6.3.1  shiro提供的realm

 

 

 

最基礎的是Realm接口,CachingRealm負責緩存處理,AuthenticationRealm負責認證,AuthorizingRealm負責受權,一般自定義的realm繼承AuthorizingRealm。

 

6.3.2  自定義Realm

 

public class CustomRealm1 extends AuthorizingRealm {

 

    @Override

    public String getName() {

       return "customRealm1";

    }

 

    //支持UsernamePasswordToken

    @Override

    public boolean supports(AuthenticationToken token) {

       return token instanceof UsernamePasswordToken;

    }

 

    //認證

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(

           AuthenticationToken token) throws AuthenticationException {

      

       //從token中 獲取用戶身份信息

       String username = (String) token.getPrincipal();

       //拿username從數據庫中查詢

       //....

       //若是查詢不到則返回null

       if(!username.equals("zhang")){//這裏模擬查詢不到

           return null;

       }

      

       //獲取從數據庫查詢出來的用戶密碼

       String password = "123";//這裏使用靜態數據模擬。。

      

       //返回認證信息由父類AuthenticatingRealm進行認證

       SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(

              username, password, getName());

 

       return simpleAuthenticationInfo;

    }

 

    //受權

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(

           PrincipalCollection principals) {

       // TODO Auto-generated method stub

       return null;

    }

 

}
View Code

 

6.3.3  shiro-realm.ini

[main]

#自定義 realm

customRealm=cn.itcast.shiro.authentication.realm.CustomRealm1

#將realm設置到securityManager

securityManager.realms=$customRealm

 

 

思考:這裏爲何不用配置[users]了??

 

6.3.4  測試代碼

 

測試代碼同入門程序,將ini的地址修改成shiro-realm.ini。

 

分別模擬帳號不存在、密碼錯誤、帳號和密碼正確進行測試。

 

6.4    散列算法

         散列算法通常用於生成一段文本的摘要信息,散列算法不可逆,將內容能夠生成摘要,沒法將摘要轉成原始內容。散列算法經常使用於對密碼進行散列,經常使用的散列算法有MD五、SHA。

通常散列算法須要提供一個salt(鹽)與原始內容生成摘要信息,這樣作的目的是爲了安全性,好比:111111的md5值是:96e79218965eb72c92a549dd5a330112,拿着「96e79218965eb72c92a549dd5a330112」去md5破解網站很容易進行破解,若是要是對111111和salt(鹽,一個隨機數)進行散列,這樣雖然密碼都是111111加不一樣的鹽會生成不一樣的散列值。

 

6.4.1  例子

 

//md5加密,不加鹽

       String password_md5 = new Md5Hash("111111").toString();

       System.out.println("md5加密,不加鹽="+password_md5);

      

       //md5加密,加鹽,一次散列

       String password_md5_sale_1 = new Md5Hash("111111", "eteokues", 1).toString();

       System.out.println("password_md5_sale_1="+password_md5_sale_1);

       String password_md5_sale_2 = new Md5Hash("111111", "uiwueylm", 1).toString();

       System.out.println("password_md5_sale_2="+password_md5_sale_2);

       //兩次散列至關於md5(md5())

 

       //使用SimpleHash

       String simpleHash = new SimpleHash("MD5", "111111", "eteokues",1).toString();

       System.out.println(simpleHash);

 

 
View Code

 

 

 

6.4.2  在realm中使用

 

         實際應用是將鹽和散列後的值存在數據庫中,自動realm從數據庫取出鹽和加密後的值由shiro完成密碼校驗。

 

6.4.2.1 自定義realm

 

@Override

    protected AuthenticationInfo doGetAuthenticationInfo(

           AuthenticationToken token) throws AuthenticationException {

      

       //用戶帳號

       String username = (String) token.getPrincipal();

       //根據用戶帳號從數據庫取出鹽和加密後的值

       //..這裏使用靜態數據

       //若是根據帳號沒有找到用戶信息則返回null,shiro拋出異常「帳號不存在」

      

       //按照固定規則加密碼結果 ,此密碼 要在數據庫存儲,原始密碼 是111111,鹽是eteokues

       String password = "cb571f7bd7a6f73ab004a70322b963d5";

       //鹽,隨機數,此隨機數也在數據庫存儲

       String salt = "eteokues";

      

       //返回認證信息

       SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(

              username, password, ByteSource.Util.bytes(salt),getName());

      

 

       return simpleAuthenticationInfo;

    }

 
View Code

 

6.4.2.2 realm配置

 

配置shiro-cryptography.ini

 

[main]

#定義憑證匹配器

credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher

#散列算法

credentialsMatcher.hashAlgorithmName=md5

#散列次數

credentialsMatcher.hashIterations=1

 

#將憑證匹配器設置到realm

customRealm=cn.itcast.shiro.authentication.realm.CustomRealm2

customRealm.credentialsMatcher=$credentialsMatcher

securityManager.realms=$customRealm

 

6.4.2.3 測試代碼

測試代碼同上個章節,注意修改ini路徑。

 

shiro受權

 

7.1    受權流程

7.2    受權方式

Shiro 支持三種方式的受權:

n  編程式:經過寫if/else 受權代碼塊完成:

Subject subject = SecurityUtils.getSubject();

if(subject.hasRole(「admin」)) {

//有權限

} else {

//無權限

}

n  註解式:經過在執行的Java方法上放置相應的註解完成:

@RequiresRoles("admin")

public void hello() {

//有權限

}

n  JSP/GSP 標籤:在JSP/GSP 頁面經過相應的標籤完成:

<shiro:hasRole name="admin">

<!— 有權限—>

</shiro:hasRole>

 

本教程序受權測試使用第一種編程方式,實際與web系統集成使用後兩種方式。

 

7.3    受權測試

7.3.1  shiro-permission.ini

建立存放權限的配置文件shiro-permission.ini,以下:

 

[users]

#用戶zhang的密碼是123,此用戶具備role1和role2兩個角色

zhang=123,role1,role2

wang=123,role2

 

[roles]

#角色role1對資源user擁有create、update權限

role1=user:create,user:update

#角色role2對資源user擁有create、delete權限

role2=user:create,user:delete

#角色role3對資源user擁有create權限

role3=user:create

 

在ini文件中用戶、角色、權限的配置規則是:「用戶名=密碼,角色1,角色2...」 「角色=權限1,權限2...」,首先根據用戶名找角色,再根據角色找權限,角色是權限集合。

 

 

 

7.3.2  權限字符串規則

         權限字符串的規則是:「資源標識符:操做:資源實例標識符」,意思是對哪一個資源的哪一個實例具備什麼操做,「:」是資源/操做/實例的分割符,權限字符串也可使用*通配符。

 

例子:

用戶建立權限:user:create,或user:create:*

用戶修改實例001的權限:user:update:001

用戶實例001的全部權限:user:*:001

 

 

7.3.3  測試代碼

 

         測試代碼同認證代碼,注意ini地址改成shiro-permission.ini,主要學習下邊受權的方法,注意:在用戶認證經過後執行下邊的受權代碼。

 

@Test

    public void testPermission() {

 

       // 從ini文件中建立SecurityManager工廠

       Factory<SecurityManager> factory = new IniSecurityManagerFactory(

              "classpath:shiro-permission.ini");

 

       // 建立SecurityManager

       SecurityManager securityManager = factory.getInstance();

 

       // 將securityManager設置到運行環境

       SecurityUtils.setSecurityManager(securityManager);

 

       // 建立主體對象

       Subject subject = SecurityUtils.getSubject();

 

       // 對主體對象進行認證

       // 用戶登錄

       // 設置用戶認證的身份(principals)和憑證(credentials)

       UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

       try {

           subject.login(token);

       } catch (AuthenticationException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

 

       // 用戶認證狀態

       Boolean isAuthenticated = subject.isAuthenticated();

 

       System.out.println("用戶認證狀態:" + isAuthenticated);

 

       // 用戶受權檢測 基於角色受權

       // 是否有某一個角色

       System.out.println("用戶是否擁有一個角色:" + subject.hasRole("role1"));

       // 是否有多個角色

       System.out.println("用戶是否擁有多個角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

      

//     subject.checkRole("role1");

//     subject.checkRoles(Arrays.asList("role1", "role2"));

 

       // 受權檢測,失敗則拋出異常

       // subject.checkRole("role22");

 

       // 基於資源受權

       System.out.println("是否擁有某一個權限:" + subject.isPermitted("user:delete"));

       System.out.println("是否擁有多個權限:" + subject.isPermittedAll("user:create:1",    "user:delete"));

      

       //檢查權限

       subject.checkPermission("sys:user:delete");

       subject.checkPermissions("user:create:1","user:delete");

      

 

    }
View Code

 

 

 

7.3.4  基於角色的受權

// 用戶受權檢測 基於角色受權

       // 是否有某一個角色

       System.out.println("用戶是否擁有一個角色:" + subject.hasRole("role1"));

       // 是否有多個角色

       System.out.println("用戶是否擁有多個角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

            

 

對應的check方法:

subject.checkRole("role1");

subject.checkRoles(Arrays.asList("role1", "role2"));

 

上邊check方法若是受權失敗則拋出異常:

org.apache.shiro.authz.UnauthorizedException: Subject does not have role [.....]

        

 

 

7.3.5  基於資源受權

 

// 基於資源受權

       System.out.println("是否擁有某一個權限:" + subject.isPermitted("user:delete"));

       System.out.println("是否擁有多個權限:" + subject.isPermittedAll("user:create:1",    "user:delete"));

對應的check方法:

subject.checkPermission("sys:user:delete");

subject.checkPermissions("user:create:1","user:delete");

 

 

上邊check方法若是受權失敗則拋出異常:

org.apache.shiro.authz.UnauthorizedException: Subject does not have permission [....]

        

 

 

7.4    自定義realm

         與上邊認證自定義realm同樣,大部分狀況是要從數據庫獲取權限數據,這裏直接實現基於資源的受權。

7.4.1  realm代碼

 

         在認證章節寫的自定義realm類中完善doGetAuthorizationInfo方法,此方法須要完成:根據用戶身份信息從數據庫查詢權限字符串,由shiro進行受權。

// 受權

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(

           PrincipalCollection principals) {

       // 獲取身份信息

       String username = (String) principals.getPrimaryPrincipal();

       // 根據身份信息從數據庫中查詢權限數據

       //....這裏使用靜態數據模擬

       List<String> permissions = new ArrayList<String>();

       permissions.add("user:create");

       permissions.add("user:delete");

      

       //將權限信息封閉爲AuthorizationInfo

      

       SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

       for(String permission:permissions){

           simpleAuthorizationInfo.addStringPermission(permission);

       }

      

       return simpleAuthorizationInfo;

    }
View Code

7.4.2  shiro-realm.ini

ini配置文件還使用認證階段使用的,不用改變。

思考:shiro-permission.ini中的[roles]爲何不須要了??

 

7.4.3  測試代碼

同上邊的受權測試代碼,注意修改ini地址爲shiro-realm.ini。

 

 

7.4.4  受權執行流程

 

一、  執行subject.isPermitted("user:create")

二、  securityManager經過ModularRealmAuthorizer進行受權

三、  ModularRealmAuthorizer調用realm獲取權限信息

四、  ModularRealmAuthorizer再經過permissionResolver解析權限字符串,校驗是否匹配

 

 

 

 shiro與項目集成開發

 

8.1    shiro與spring web項目整合

 

         shiro與springweb項目整合在「基於url攔截實現的工程」基礎上整合,基於url攔截實現的工程的技術架構是springmvc+mybatis,整合注意兩點:

         一、shiro與spring整合

         二、加入shiro對web應用的支持

 

8.1.1  取消原springmvc認證和受權攔截器

去掉springmvc.xml中配置的LoginInterceptor和PermissionInterceptor攔截器。

 

8.1.2  加入shiro的 jar包

 

 

8.1.3  web.xml添加shiro Filter

 

<!-- shiro過慮器,DelegatingFilterProxy經過代理模式將spring容器中的bean和filter關聯起來 -->

    <filter>

       <filter-name>shiroFilter</filter-name>

       <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

       <!-- 設置true由servlet容器控制filter的生命週期 -->

       <init-param>

           <param-name>targetFilterLifecycle</param-name>

           <param-value>true</param-value>

       </init-param>

       <!-- 設置spring容器filter的bean id,若是不設置則找與filter-name一致的bean-->

       <init-param>

           <param-name>targetBeanName</param-name>

           <param-value>shiroFilter</param-value>

       </init-param>

    </filter>

    <filter-mapping>

       <filter-name>shiroFilter</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>
View Code

 

 

 

8.1.4  applicationContext-shiro.xml

 

<!-- Shiro 的Web過濾器 -->

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

       <property name="securityManager" ref="securityManager" />

       <!-- loginUrl認證提交地址,若是沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->

       <property name="loginUrl" value="/login.action" />

       <property name="unauthorizedUrl" value="/refuse.jsp" />

       <!-- 過慮器鏈定義,從上向下順序執行,通常將/**放在最下邊 -->

       <property name="filterChainDefinitions">

           <value>

              <!-- 退出攔截,請求logout.action執行退出操做 -->

              /logout.action = logout

              <!-- 無權訪問頁面 -->

              /refuse.jsp = anon

              <!-- roles[XX]表示有XX角色纔可訪問 -->

              /item/list.action = roles[item],authc

              /js/** anon

              /images/** anon

              /styles/** anon

              /validatecode.jsp anon

              /item/* authc

              <!-- user表示身份認證經過或經過記住我認證經過的能夠訪問 -->

              /** = authc

           </value>

       </property>

    </bean>

 

    <!-- 安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

       <property name="realm" ref="userRealm" />

    </bean>

 

    <!-- 自定義 realm -->

    <bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">

    </bean>

   

 

 

 
View Code

 

securityManager:這個屬性是必須的。

loginUrl:沒有登陸認證的用戶請求將跳轉到此地址進行認證,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面。

unauthorizedUrl:沒有權限默認跳轉的頁面。

 

 

 

8.1.5  自定義realm

 

此realm先不從數據庫查詢權限數據,當前須要先將shiro整合完成,在上邊章節定義的realm基礎上修改。

 

public class CustomRealm1 extends AuthorizingRealm {

 

    @Override

    public String getName() {

       return "customRealm";

    }

 

    // 支持什麼類型的token

    @Override

    public boolean supports(AuthenticationToken token) {

       return token instanceof UsernamePasswordToken;

    }

 

    // 認證

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(

           AuthenticationToken token) throws AuthenticationException {

 

       // 從token中 獲取用戶身份信息

       String username = (String) token.getPrincipal();

       // 拿username從數據庫中查詢

       // ....

       // 若是查詢不到則返回null

       if (!username.equals("zhang")) {// 這裏模擬查詢不到

           return null;

       }

 

       // 獲取從數據庫查詢出來的用戶密碼

       String password = "123";// 這裏使用靜態數據模擬。。

      

       // 根據用戶id從數據庫取出菜單

       //...先用靜態數據

       List<SysPermission> menus = new ArrayList<SysPermission>();;

      

       SysPermission sysPermission_1 = new SysPermission();

       sysPermission_1.setName("商品管理");

       sysPermission_1.setUrl("/item/queryItem.action");

       SysPermission sysPermission_2 = new SysPermission();

       sysPermission_2.setName("用戶管理");

       sysPermission_2.setUrl("/user/query.action");

      

       menus.add(sysPermission_1);

       menus.add(sysPermission_2);

      

       // 構建用戶身份信息

       ActiveUser activeUser = new ActiveUser();

       activeUser.setUserid(username);

       activeUser.setUsername(username);

       activeUser.setUsercode(username);

       activeUser.setMenus(menus);

 

       // 返回認證信息由父類AuthenticatingRealm進行認證

       SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(

              activeUser, password, getName());

 

       return simpleAuthenticationInfo;

    }

 

    // 受權

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(

           PrincipalCollection principals) {

       // 獲取身份信息

       ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();

       //用戶id

       String userid = activeUser.getUserid();

       // 根據用戶id從數據庫中查詢權限數據

       // ....這裏使用靜態數據模擬

       List<String> permissions = new ArrayList<String>();

       permissions.add("item:query");

       permissions.add("item:update");

 

       // 將權限信息封閉爲AuthorizationInfo

 

       SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

       for (String permission : permissions) {

           simpleAuthorizationInfo.addStringPermission(permission);

       }

 

       return simpleAuthorizationInfo;

    }

 

}

 
View Code

 

 

 

8.1.6  登陸

 

// 用戶登錄提交

    @RequestMapping("/login")

    public String loginsubmit(Model model, HttpServletRequest request)

           throws Exception {

 

       // shiro在認證過程當中出現錯誤後將異常類路徑經過request返回

       String exceptionClassName = (String) request

              .getAttribute("shiroLoginFailure");

       if(exceptionClassName!=null){

           if (UnknownAccountException.class.getName().equals(exceptionClassName)) {

              throw new CustomException("帳號不存在");

           } else if (IncorrectCredentialsException.class.getName().equals(

                  exceptionClassName)) {

              throw new CustomException("用戶名/密碼錯誤");

           } else if("randomCodeError".equals(exceptionClassName)){

              throw new CustomException("驗證碼錯誤");

           } else{

              throw new Exception();//最終在異常處理器生成未知錯誤

           }

       }

       return "login";

      

    }
View Code

 

 

 

8.1.7  首頁

 

    因爲session由shiro管理,須要修改首頁的controller方法,將session中的數據經過model傳到頁面。

 

//系統首頁

    @RequestMapping("/first")

    public String first(Model model)throws Exception{

      

       //主體

       Subject subject = SecurityUtils.getSubject();

       //身份

       ActiveUser activeUser = (ActiveUser) subject.getPrincipal();

       model.addAttribute("activeUser", activeUser);

       return "/first";

    }

 

 

8.1.8  退出

 

    因爲使用shiro的sessionManager,不用開發退出功能,使用shiro的logout攔截器便可。

 

<!-- 退出攔截,請求logout.action執行退出操做 -->

/logout.action = logout

 

 

8.1.9  無權限refuse.jsp

 

當用戶無操做權限,shiro將跳轉到refuse.jsp頁面。

 

 

8.1.10 shiro過慮器總結

 

過濾器簡稱

對應的java類

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user

org.apache.shiro.web.filter.authc.UserFilter

logout

org.apache.shiro.web.filter.authc.LogoutFilter

 

 

anon:例子/admins/**=anon 沒有參數,表示能夠匿名使用。

authc:例如/admins/user/**=authc表示須要認證(登陸)才能使用,FormAuthenticationFilter是表單認證,沒有參數

roles:例子/admins/user/**=roles[admin],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每一個參數經過纔算經過,至關於hasAllRoles()方法。

perms:例子/admins/user/**=perms[user:add:*],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每一個參數都經過才經過,想當於isPermitedAll()方法。

rest:例子/admins/user/**=rest[user],根據請求的方法,至關於/admins/user/**=perms[user:method] ,其中method爲post,get,delete等。

port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString

是你訪問的url裏的?後面的參數。

authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證

 

ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https

user:例如/admins/user/**=user沒有參數表示必須存在用戶, 身份認證經過或經過記住我認證經過的能夠訪問,當登入操做時不作檢查

注:

anon,authcBasic,auchc,user是認證過濾器,

perms,roles,ssl,rest,port是受權過濾器

 

 

8.2    認證

8.2.1  添加憑證匹配器

添加憑證匹配器實現md5加密校驗。

修改applicationContext-shiro.xml:

 

<!-- 憑證匹配器 -->

    <bean id="credentialsMatcher"

       class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">

       <property name="hashAlgorithmName" value="md5" />

       <property name="hashIterations" value="1" />

    </bean>

 

<!-- 自定義 realm -->

    <bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">

       <property name="credentialsMatcher" ref="credentialsMatcher" />

    </bean>

 

 

8.2.2  修改realm認證方法

修改realm代碼從數據庫中查詢用戶身份信息,將sysService注入realm。

 

 

public class CustomRealm1 extends AuthorizingRealm {

 

    @Autowired

    private SysService sysService;

 

    @Override

    public String getName() {

       return "customRealm";

    }

 

    // 支持什麼類型的token

    @Override

    public boolean supports(AuthenticationToken token) {

       return token instanceof UsernamePasswordToken;

    }

 

    @Override

    protected AuthenticationInfo doGetAuthenticationInfo(

           AuthenticationToken token) throws AuthenticationException {

       // 從token中獲取用戶身份

       String usercode = (String) token.getPrincipal();

 

       SysUser sysUser = null;

       try {

           sysUser = sysService.findSysuserByUsercode(usercode);

       } catch (Exception e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

 

       // 若是帳號不存在

       if (sysUser == null) {

           return null;

       }

 

       // 根據用戶id取出菜單

       List<SysPermission> menus = null;

       try {

           menus = sysService.findMenuList(sysUser.getId());

       } catch (Exception e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

       // 用戶密碼

       String password = sysUser.getPassword();

       //

       String salt = sysUser.getSalt();

      

       // 構建用戶身體份信息

       ActiveUser activeUser = new ActiveUser();

       activeUser.setUserid(sysUser.getId());

       activeUser.setUsername(sysUser.getUsername());

       activeUser.setUsercode(sysUser.getUsercode());

       activeUser.setMenus(menus);

      

       SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(

              activeUser, password, ByteSource.Util.bytes(salt),getName());

      

       return simpleAuthenticationInfo;

    }

 

    .....

 

}
View Code

8.3    受權

 

8.3.1  修改realm受權方法

修改realm代碼從數據庫中查詢權限信息,將sysService注入realm。

public class CustomRealm1 extends AuthorizingRealm {

 

    @Autowired

    private SysService sysService;

 

    @Override

    public String getName() {

       return "customRealm";

    }

 

    // 支持什麼類型的token

    @Override

    public boolean supports(AuthenticationToken token) {

       return token instanceof UsernamePasswordToken;

    }

 

 

    @Override

    protected AuthorizationInfo doGetAuthorizationInfo(

           PrincipalCollection principals) {

      

       //身份信息

       ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();

       //用戶id

       String userid = activeUser.getUserid();

       //獲取用戶權限

       List<SysPermission> permissions = null;

       try {

           permissions = sysService.findSysPermissionList(userid);

       } catch (Exception e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

       //構建shiro受權信息

       SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

       for(SysPermission sysPermission:permissions){

           simpleAuthorizationInfo.addStringPermission(sysPermission.getPercode());

       }

      

       return simpleAuthorizationInfo;

      

    }

 

}
View Code

 

 

8.3.2  對controller開啓AOP

 

在springmvc.xml中配置shiro註解支持,可在controller方法中使用shiro註解配置權限:

 

<!-- 開啓aop,對類代理 -->

    <aop:config proxy-target-class="true"></aop:config>

    <!-- 開啓shiro註解支持 -->

    <bean

       class="

org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

       <property name="securityManager" ref="securityManager" />

    </bean>

 

 

 

 

8.3.3  權限註解控制

商品查詢controller方法添加權限(item:query):

 

// 查詢商品列表

    @RequestMapping("/queryItem")

    @RequiresPermissions("item:query")

    public ModelAndView queryItem() throws Exception {

上邊代碼@RequiresPermissions("item:query")表示必須擁有「item:query」權限方可執行。

 

同理,商品修改controller方法添加權限(item:update):

 

@RequestMapping(value = "/editItem")

    @RequiresPermissions("item:update")

    public String editItem(@RequestParam(value = "id", required = true) Integer id, Model model) throws Exception

 

// 商品修改提交

    @RequestMapping("/editItemSubmit")

    @RequiresPermissions("item:update")

    public String editItemSubmit(@ModelAttribute("item") Items items,BindingResult result,

           MultipartFile pictureFile,Model model,HttpServletRequest request)

           throws Exception

 

 

 

8.3.4  jsp標籤控制

 

8.3.4.1 標籤介紹

 

Jsp頁面添加:

<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>

 

標籤名稱

標籤條件(均是顯示標籤內容)

<shiro:authenticated>

登陸以後

<shiro:notAuthenticated>

不在登陸狀態時

<shiro:guest>

用戶在沒有RememberMe時

<shiro:user>

用戶在RememberMe時

<shiro:hasAnyRoles name="abc,123" >

在有abc或者123角色時

<shiro:hasRole name="abc">

擁有角色abc

<shiro:lacksRole name="abc">

沒有角色abc

<shiro:hasPermission name="abc">

擁有權限資源abc

<shiro:lacksPermission name="abc">

沒有abc權限資源

<shiro:principal>

顯示用戶身份名稱

 <shiro:principal property="username"/>     顯示用戶身份中的屬性值

 

8.3.4.2 jsp頁面添加標籤

若是有商品修改權限頁面顯示「修改」連接。

 

<shiro:hasPermission name="item:update">

    <a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a>

    </shiro:hasPermission>

 

 

8.4    緩存

   shiro每次受權都會經過realm獲取權限信息,爲了提升訪問速度須要添加緩存,第一次從realm中讀取權限數據,以後再也不讀取,這裏Shiro和Ehcache整合。

 

8.4.1  添加Ehcache的jar包

 

 

 

8.4.2  配置cacheManager

在applicationContext-shiro.xml中配置緩存管理器。

<!-- 安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

       <property name="realm" ref="userRealm" />

       <property name="cacheManager" ref="cacheManager"/>

    </bean>

 

<!-- 緩存管理器 -->

<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">

        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>

    </bean>

 

8.4.3  配置shiro-ehcache.xml

 

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <!--diskStore:緩存數據持久化的目錄 地址  -->

    <diskStore path="F:\develop\ehcache" />

    <defaultCache

       maxElementsInMemory="1000"

       maxElementsOnDisk="10000000"

       eternal="false"

       overflowToDisk="false"

       diskPersistent="false"

       timeToIdleSeconds="120"

       timeToLiveSeconds="120"

       diskExpiryThreadIntervalSeconds="120"

       memoryStoreEvictionPolicy="LRU">

    </defaultCache>

</ehcache>

 
View Code

 

 

8.4.4  清空緩存

 

   當用戶權限修改後,用戶再次登錄shiro會自動調用realm從數據庫獲取權限數據,若是在修改權限後想當即清除緩存則能夠調用realm的clearCache方法清除緩存。

 

   realm中定義clearCached方法:

 

//清除緩存

    public void clearCached() {

       PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();

       super.clearCache(principals);

    }

 

在權限修改後調用realm中的方法,realm已經由spring管理,因此從spring中獲取realm實例,調用clearCached方法。

 

 

 

8.5    session管理

在applicationContext-shiro.xml中配置sessionManager:

<!-- 安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

       <property name="realm" ref="userRealm" />

       <property name="sessionManager" ref="sessionManager" />

    </bean>

<!-- 會話管理器 -->

    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">

        <!-- session的失效時長,單位毫秒 -->

        <property name="globalSessionTimeout" value="600000"/>

        <!-- 刪除失效的session -->

        <property name="deleteInvalidSessions" value="true"/>

    </bean>

 

 

 

8.6    驗證碼

 

8.6.1  自定義FormAuthenticationFilter

須要在驗證帳號和名稱以前校驗驗證碼。

 

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {

    protected boolean onAccessDenied(ServletRequest request,

           ServletResponse response, Object mappedValue) throws Exception {

 

       // 校驗驗證碼

        // 從session獲取正確的驗證碼

       HttpSession session = ((HttpServletRequest)request).getSession();

       //頁面輸入的驗證碼

       String randomcode = request.getParameter("randomcode");

       //從session中取出驗證碼

       String validateCode = (String) session.getAttribute("validateCode");

       if (randomcode!=null && validateCode!=null) {

           if (!randomcode.equals(validateCode)) {

               // randomCodeError表示驗證碼錯誤

               request.setAttribute("shiroLoginFailure", "randomCodeError");

               //拒絕訪問,再也不校驗帳號和密碼

               return true;

           }

       }

       return super.onAccessDenied(request, response, mappedValue);

    }

}

 

8.6.2  FormAuthenticationFilter配置

修改applicationContext-shiro.xml中對FormAuthenticationFilter的配置。

 

n  在shiroFilter中添加filters:

 

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

       <property name="filters">

           <map>

              <!-- FormAuthenticationFilter是基於表單認證的過慮器 -->

              <entry key="authc" value-ref="formAuthenticationFilter" />

           </map>

       </property>

.....

.....

 

 

n  formAuthenticationFilter定義

 

<!-- 基於Form表單的身份驗證過濾器,不配置將也會註冊此過慮器,表單中的用戶帳號、密碼及loginurl將採用默認值,建議配置 -->

    <bean id="formAuthenticationFilter"

    class="org.apache.shiro.web.filter.authc.MyFormAuthenticationFilter ">

       <!-- 表單中帳號的input名稱 -->

       <property name="usernameParam" value="username" />

       <!-- 表單中密碼的input名稱 -->

       <property name="passwordParam" value="password" />

 </bean>

 

 

 

8.6.3  登錄頁面

添加驗證碼:

<TR>

                         <TD>驗證碼:</TD>

                         <TD><input id="randomcode" name="randomcode" size="8" /> <img

                            id="randomcode_img" src="${baseurl}validatecode.jsp" alt=""

                            width="56" height="20" align='absMiddle' /> <a

                            href=javascript:randomcode_refresh()>刷新</a></TD>

                     </TR>

 

8.6.4  配置validatecode.jsp匿名訪問

 

修改applicationContext-shiro.xml:

 

 

 

8.7    記住我

         用戶登錄選擇「自動登錄」本次登錄成功會向cookie寫身份信息,下次登錄從cookie中取出身份信息實現自動登錄。

8.7.1  用戶身份實現java.io.Serializable接口

向cookie記錄身份信息須要用戶身份信息對象實現序列化接口,以下:

 

 

 

 

 

8.7.2  配置rememberMeManager

 

<!-- 安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

       <property name="realm" ref="userRealm" />

       <property name="sessionManager" ref="sessionManager" />

       <property name="cacheManager" ref="cacheManager"/>

       <!-- 記住我 -->

       <property name="rememberMeManager" ref="rememberMeManager"/>

    </bean>

 

<!-- rememberMeManager管理器 -->

    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">

       <property name="cookie" ref="rememberMeCookie" />

    </bean>

    <!-- 記住我cookie -->

    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">

       <constructor-arg value="rememberMe" />

       <!-- 記住我cookie生效時間30天 -->

       <property name="maxAge" value="2592000" />

    </bean>

 

 

8.7.3  FormAuthenticationFilter配置

 

修改formAuthenticationFitler添加頁面中「記住我checkbox」的input名稱:

 

<bean id="formAuthenticationFilter"

       class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">

       <!-- 表單中帳號的input名稱 -->

       <property name="usernameParam" value="usercode" />

       <!-- 表單中密碼的input名稱 -->

       <property name="passwordParam" value="password" />

       <property name="rememberMeParam" value="rememberMe"/>

    </bean>

 

 

8.7.4  登錄頁面

在login.jsp中添加「記住我」checkbox。

 

<TR>

                         <TD></TD>

                         <TD>

                         <input type="checkbox" name="rememberMe" />自動登錄

                         </TD>

                     </TR>

相關文章
相關標籤/搜索