1. 程式人生 > >spring-boot整合shiro

spring-boot整合shiro

概述

許可權體系在現代任何IT系統中都是很基礎但又非常重要的部分,無論是傳統MIS系統還是網際網路系統,出於保護業務資料和應用自身的安全,都會設計自己的授權鑑權策略。

最近專案中也需要用到許可權驗證功能,專案為spring-boot工程,現成的許可權驗證框架有shiro和spring-security,shiro相對spring-security來說學習難度要低一點,也是老牌成熟的產品,因此選擇shiro作為專案的許可權驗證框架。

shiro介紹

Shiro是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理等功能。

image

Authentication

:身份認證/登入,驗證使用者是不是擁有相應的身份;

Authorization:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權;即判斷使用者是否能做事情,常見的如:驗證某個使用者是否擁有某個角色。或者細粒度的驗證某個使用者對某個資源是否具有某個許可權;

Session Manager:會話管理,即使用者登入後就是一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的;

Cryptography:加密,保護資料的安全性,如密碼加密儲存到資料庫,而不是明文儲存;

Web Support:Web支援,可以非常容易的整合到Web環境;

Caching:快取,比如使用者登入後,其使用者資訊、擁有的角色/許可權不必每次去查,這樣可以提高效率;

Concurrency:shiro支援多執行緒應用的併發驗證,即如在一個執行緒中開啟另一個執行緒,能把許可權自動傳播過去;

Testing:提供測試支援;

Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行訪問;

Remember Me:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登入了。

記住一點,Shiro不會去維護使用者、維護許可權;這些需要我們自己去設計/提供;然後通過相應的介面注入給Shiro即可。

shiro鑑權過程

首先,我們從外部來看Shiro吧,即從應用程式角度的來觀察如何使用Shiro完成工作。如下圖:

image

可以看到:應用程式碼直接互動的物件是Subject,也就是說Shiro的對外API核心就是Subject;其每個API的含義:

Subject:主體,代表了當前“使用者”,這個使用者不一定是一個具體的人,與當前應用互動的任何東西都是Subject,如網路爬蟲,機器人等;即一個抽象概念;所有Subject都繫結到SecurityManager,與Subject的所有互動都會委託給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;

SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager互動;且它管理著所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他元件進行互動,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro從從Realm獲取安全資料(如使用者、角色、許可權),就是說SecurityManager要驗證使用者身份,那麼它需要從Realm獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從Realm得到使用者相應的角色/許可權進行驗證使用者是否能進行操作;可以把Realm看成DataSource,即安全資料來源。

shiro架構

image

Subject:主體,可以看到主體可以是任何可以與應用互動的“使用者”;

SecurityManager:相當於SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;所有具體的互動都通過SecurityManager進行控制;它管理著所有Subject、且負責進行認證和授權、及會話、快取的管理。

Authenticator:認證器,負責主體認證的,這是一個擴充套件點,如果使用者覺得Shiro預設的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什麼情況下算使用者認證通過了;

Authrizer:授權器,或者訪問控制器,用來決定主體是否有許可權進行相應的操作;即控制著使用者能訪問應用中的哪些功能;

Realm:可以有1個或多個Realm,可以認為是安全實體資料來源,即用於獲取安全實體的;可以是JDBC實現,也可以是LDAP實現,或者記憶體實現等等;由使用者提供;注意:Shiro不知道你的使用者/許可權儲存在哪及以何種格式儲存;所以我們一般在應用中都需要實現自己的Realm;

SessionManager:如果寫過Servlet就應該知道Session的概念,Session呢需要有人去管理它的生命週期,這個元件就是SessionManager;而Shiro並不僅僅可以用在Web環境,也可以用在如普通的JavaSE環境、EJB等環境;所有呢,Shiro就抽象了一個自己的Session來管理主體與應用之間互動的資料;這樣的話,比如我們在Web環境用,剛開始是一臺Web伺服器;接著又上了臺EJB伺服器;這時想把兩臺伺服器的會話資料放到一個地方,這個時候就可以實現自己的分散式會話(如把資料放到Memcached伺服器);

SessionDAO:DAO大家都用過,資料訪問物件,用於會話的CRUD,比如我們想把Session儲存到資料庫,那麼可以實現自己的SessionDAO,通過如JDBC寫到資料庫;比如想把Session放到Memcached中,可以實現自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache進行快取,以提高效能;

CacheManager:快取控制器,來管理如使用者、角色、許可權等的快取的;因為這些資料基本上很少去改變,放到快取中後可以提高訪問的效能

Cryptography:密碼模組,Shiro提高了一些常見的加密元件用於如密碼加密/解密的。

spring-boot-shiro-demo

本demo工程為maven工程專案,工程技術架構採用spring-boot + mybatis + mysql 。

資料庫設計

image

工程結構

image

包說明

  • common :配置類、列舉、異常、工具類等。
  • controller :控制層,頁面實際請求入口。鑑權註解加在這一層。
  • mapper:存放mybatis介面類
  • model : 存放資料庫實體類
  • service :服務層,在這一層編寫具體業務邏輯程式碼。
  • vo :前後端互動實體類。
  • ItcljApplication :spring-boot 啟動類。

核心程式碼說明

由於工作實際專案需求是介面鑑權,票據採用token,所以採用shiro的token驗證方式,shiro預設token鑑權的時候token是從cokies裡面取,工作實際專案中token是放在一個自定義http header裡面,所以重寫了shiro session manager,以使token和shiro session建立對映關係,這個對映關係本demo是儲存在redis叢集中。

image

  • ShiroConfiguration :shiro配置型別,例項化Realm、SecurityManager、SessionManager等
  • AjaxPermissionsAuthorizationFilter:自定義ajax鑑權過濾器,所有返回都採用JSON格式。
  • ShiroSessionManager:自定義session manager,以使自定義token能夠和shiro session繫結,這個對映關係存放在redis叢集中。
  • UserRealm:使用者域,從mysql資料庫獲取使用者許可權,並顯示設定給shiro。

MainController

 /**
     * 登入
     *
     * shiro登入,shiro採用Facade模式(門面模式),所有與shiro的互動都通過Subject物件API。
     * 呼叫Subject.login後會觸發UserRealm的doGetAuthenticationInfo方法,進行具體的登入驗證處理。
     *
     * @param username 使用者名稱
     * @param password 密碼
     * @return
     */
    @RequestMapping("/login")
    public ResponseData login(String username, String password) {
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            currentUser.login(token);//會觸發com.itclj.common.shiro.UserRealm的doGetAuthenticationInfo方法
            Session session = SecurityUtils.getSubject().getSession();
            UserVO userVO = (UserVO) session.getAttribute(Constants.SESSION_USER_INFO);
            jedisCluster.setex(Constants.REDIS_KEY_PREFIX_SHIRO_TOKEN + userVO.getToken(),
                    Constants.REDIS_SHIRO_TOKEN_EXPIRES,
                    session.getId().toString());
            return new ResponseData(userVO);
        } catch (AuthenticationException e) {
            return new ResponseData("登入失敗");
        }
    }

SysUserController

    /**
     * 獲取使用者詳細
     *
     * @param userid 使用者ID
     * @return
     */
    @GetMapping("/{userid}")
    @RequiresPermissions("user:detail")//具有 user:detail 許可權的使用者才能訪問此方法
    public ResponseData detail(@PathVariable("userid") Integer userid) {
        return new ResponseData(sysUserService.detail(userid));
    }

完整工程程式碼

https://github.com/clj198606061111/spring-boot-shiro-demo.git


參考