Springboot 整合ApachShiro完成登錄驗證和權限管理


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中文參考手冊

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM