Springboot 整合ApachShiro完成登入驗證和許可權管理

朝北發表於2019-06-06

1.前言

做一個系統最大的問題就是安全問題以及許可權的問題,如何正確的選擇一個安全框架對自己的系統進行保護,這方面常用的框架有SpringSecurity,但考慮到它的龐大和複雜,大多數公司還是會選擇

ApachShiro 這個框架作為公司安全的框架,這裡我們掌握這個框架也是特別有必要的,所以,開始搭建!

2.簡介 shiro能做什麼

  • 驗證使用者身份
  • 使用者訪問許可權控制,比如:
    • 判斷使用者是否分配了一定的安全形色。
    • 判斷使用者是否被授予完成某個操作的許可權
  • 在非 web 或 EJB 容器的環境下可以任意使用Session API
  • 可以響應認證、訪問控制,或者 Session 生命週期中發生的事件
  • 可將一個或以上使用者安全資料來源資料組合成一個複合的使用者 "view"(檢視)
  • 支援單點登入(SSO)功能
  • 支援提供“Remember Me”服務,獲取使用者關聯資訊而無需登入

3.shiro 功能簡介

  • Authentication(認證):使用者身份識別,通常被稱為使用者“登入”
  • Authorization(授權):訪問控制。比如某個使用者是否具有某個操作的使用許可權。
  • Session Management(會話管理):特定於使用者的會話管理,甚至在非web 或 EJB 應用程式。
  • Cryptography(加密):在對資料來源使用加密演算法加密的同時,保證易於使用。

 還有其他的功能來支援和加強這些不同應用環境下安全領域的關注點。特別是對以下的功能支援:


  • Web支援:Shiro 提供的 web 支援 api ,可以很輕鬆的保護 web 應用程式的安全。
  • 快取:快取是 Apache Shiro 保證安全操作快速、高效的重要手段。
  • 併發:Apache Shiro 支援多執行緒應用程式的併發特性。
  • 測試:支援單元測試和整合測試,確保程式碼和預想的一樣安全。
  • "Run As":這個功能允許使用者假設另一個使用者的身份(在許可的前提下)。
  • "Remember Me":跨 session 記錄使用者的身份,只有在強制需要時才需要登入。

3.Architecture 架構

Apache Shiro 設計理念是使程式的安全變得簡單直觀而易於實現,Shiro的核心設計參照大多數使用者對安全的思考模式--如何對某人(或某事)在與程式互動的環境中的進行安全控制。

程式設計通常都以使用者故事為基礎,也就是說,你會經常設計使用者介面或服務api基於使用者如何(或應該)與軟體互動。 例如,你可能會說,“如果我的應用程式的使用者互動是登入,我將展示他們可以單擊一個按鈕來檢視他們的帳戶資訊。 如果不登入,我將展示一個註冊按鈕。”

4.Shiro 架構包含三個主要的理念

  • Subject:就像我們在上一章示例中提到的那樣,Subject 本質上是當前執行使用者特定的'View'(檢視),而單詞“User”經常暗指一個人,Subject 可以是一個人,但也可以是第三方服務、守護程式帳戶、時鐘守護任務或者其它--當前和軟體互動的任何事件。 Subject 例項都和(也需要)一個 SecurityManager 繫結,當你和一個Subject 進行互動,這些互動動作被轉換成 SecurityManager 下Subject 特定的互動動作。

  • SecurityManager: SecurityManager 是 Shiro 架構的核心,配合內部安全元件共同組成安全傘。然而,一旦一個程式配置好了SecurityManager 和它的內部物件,SecurityManager通常獨自留下來,程式開發人員幾乎花費的所有時間都集中在 Subjet API上。 我們將在以後詳細討論 SecurityManager,但當你和一個 Subject 互動時瞭解它是很重要的。任何 Subject 的安全操作中 SecurityManager 是幕後真正的舉重者,這在上面的圖表中可以反映出來。

  • Realms: Reamls 是 Shiro 和你的程式安全資料之間的“橋”或者“連線”,它用來實際和安全相關的資料如使用者執行身份認證(登入)的帳號和授權(訪問控制)進行互動,Shiro 從一個或多個程式配置的 Realm 中查詢這些東西。 Realm 本質上是一個特定的安全 DAO:它封裝與資料來源連線的細節,得到Shiro 所需的相關的資料。在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或授權(authorization)。SecurityManager 可以配置多個複雜的 Realm,但是至少有一個是需要的。 Shiro 提供開箱即用的 Realms 來連線安全資料來源(或叫地址)如 LDAP、JDBC、檔案配置如INI和屬性檔案等,如果已有的Realm不能滿足你的需求你也可以開發自己的Realm實現。 和其它內部元件一樣,Shiro SecurityManager 管理如何使用 Realms獲取 Subject 例項所代表的安全和身份資訊。

5.說了這麼多 瞭解一些概念後開始搭建

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>


        <!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- apache shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- spring jpa -->    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

6.配置ShiroConfig (注意導包,容易導錯)

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

@Configuration
public class ShiroConfig {
    
    /***
     * 配置shiro過濾器工廠Bean
     * @Author MRC
     * @Date 2019年6月6日 上午11:42:46
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 攔截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不會被攔截的連結 順序判斷
        filterChainDefinitionMap.put("/static/**", "anon");
        // 配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
        filterChainDefinitionMap.put("/logout", "logout");
        // <!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心程式碼就不好使了;
        // <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登入成功後要跳轉的連結
        shiroFilterFactoryBean.setSuccessUrl("/index");

        // 未授權介面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 憑證匹配器  告訴
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");// 雜湊演算法:這裡使用MD5演算法;
        hashedCredentialsMatcher.setHashIterations(2);// 雜湊的次數,比如雜湊兩次,相當於 md5(md5(""));
        return hashedCredentialsMatcher;
    }
    
    /**
     * 自定義橋----》它用來實際和安全相關的資料如使用者執行身份認證(登入)的帳號和授權(訪問控制)進行互動
     * @Author MRC
     * @Date 2019年6月6日 上午11:44:45
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        //設定密碼憑證匹配器
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }
    
    /***
     * SecurityManager 是 Shiro 架構的核心,配合內部安全元件共同組成安全傘
     * @Author MRC
     * @Date 2019年6月6日 上午11:46:23
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    /**
     * 開啟shiro aop註解支援. 使用代理方式;所以需要開啟程式碼支援;
     * 
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    
    
    @Bean(name = "simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");// 資料庫異常處理
        mappings.setProperty("UnauthorizedException", "403");
        r.setExceptionMappings(mappings); // None by default
        r.setDefaultErrorView("error"); // No default
        r.setExceptionAttribute("ex"); // Default is "exception"
        // r.setWarnLogCategory("example.MvcLogger"); // No default
        return r;
    }
}
View Code

7.配置Realms (配置認證與授權)

它用來實際和安全相關的資料如使用者執行身份認證(登入)的帳號和授權(訪問控制)進行互動,Shiro 從一個或多個程式配置的 Realm 中查詢這些東西。 Realm 本質上是一個特定的安全 DAO:它封裝與資料來源連線的細節,得到Shiro 所需的相關的資料。在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或授權(authorization)

public class MyShiroRealm extends AuthorizingRealm {

    
    @Autowired
    private UserInfoService userInfoService;
    
    /***
     * 當訪問到頁面的時候,連結配置了相應的許可權或者 Shiro 標籤才會執行此方法否則不會執行
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("許可權配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        
        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
        for(SysRole role:userInfo.getRoleList()){
            //加入角色
            authorizationInfo.addRole(role.getRole());
            for(SysPermission p:role.getPermissions()){
                //迴圈放入許可權資訊
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }

    /***
     * 重寫獲取使用者資訊的方法
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        // 獲取使用者的輸入的賬號.
        String username = (String) token.getPrincipal();
        System.out.println(token.getCredentials());
        // 通過username從資料庫中查詢 User物件,如果找到,沒找到.
        // 實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
        UserInfo userInfo = userInfoService.findByUsername(username);
        System.out.println("----->>userInfo=" + userInfo);
        if (userInfo == null) {
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 使用者名稱
                userInfo.getPassword(), // 密碼
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt
                getName() // realm name
        );
        return authenticationInfo;

    }

}

8 具體註釋都寫在程式碼裡面了,匯入sql檔案開箱即可使用Demo

碼雲:https://gitee.com/mrc1999/Springboot-apachShiro

參考:https://www.cnblogs.com/ityouknow/p/7089177.html

參考:ApachShiro中文參考手冊

 

相關文章