1. 程式人生 > >Spring Boot 整合Shiro實現登陸認證和許可權控制

Spring Boot 整合Shiro實現登陸認證和許可權控制

我在做畢設的時候,使用了Shiro作為專案中的登陸認證和許可權控制。
下面是我專案中如何實現整合shiro的學習記錄。

匯入shiro依賴包到pom.xml

  <!-- Shiro依賴 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.2.2</version>
    </dependency
>

配置shiro

下面給出了我專案中配置shiro的ShiroConfiguration 類中的主要程式碼:

package com.ciyou.edu.config.shiro.common

@Configuration
class ShiroConfiguration {

    @Autowired(required = false)
    private PermissionService permissionService

    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class)


    /**
     * ShiroFilterFactoryBean 處理攔截資原始檔問題。
     * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,因為在
     * 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager
     * Filter Chain定義說明 1、一個URL可以配置多個Filter,使用逗號分隔 2、當設定多個過濾器時,全部驗證通過,才視為通過
     *
     * 部分過濾器可指定引數,如perms,roles
     * @param
securityManager * @return */
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean() Map<String, Filter> filters = shiroFilterFactoryBean.getFilters()//獲取filters
//將自定義 的許可權驗證失敗的過濾器ShiroFilterFactoryBean注入shiroFilter filters.put("perms", new ShiroPermissionsFilter()) // 必須設定SecuritManager shiroFilterFactoryBean.setSecurityManager(securityManager) // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login") //登入成功後要跳轉的連結 //shiroFilterFactoryBean.setSuccessUrl("/index") // 許可權控制map. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>() // 配置退出過濾器,其中的具體的退出程式碼Shiro已經替我們實現了 filterChainDefinitionMap.put("/logout", "logout") filterChainDefinitionMap.put("/favicon.ico", "anon") filterChainDefinitionMap.put("/adminLogin", "anon") filterChainDefinitionMap.put("/teacherLogin", "anon") //允許訪問靜態資源 filterChainDefinitionMap.put("/static/**", "anon") // 從資料庫獲取所有的許可權 List<Permission> permissionList = permissionService?.findAllPermission() permissionList?.each {current_Permission -> //規則:"roles[admin,user]", "perms[file:edit]" filterChainDefinitionMap?.put(current_Permission?.getUrl(),"perms[" + current_Permission?.getPermission() + "]") logger.info("從資料庫載入的攔截器規則:資源路徑: " + current_Permission?.getUrl() + " ,需要許可權:" +current_Permission?.getPermission()) } filterChainDefinitionMap.put("/admin/**","roles[Admin]") filterChainDefinitionMap.put("/teacher/**","roles[Teacher]") filterChainDefinitionMap.put("/student/**","roles[Student]") // 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊 filterChainDefinitionMap.put("/**", "authc") //authc表示需要驗證身份才能訪問,還有一些比如anon表示不需要驗證身份就能訪問等。 logger.info("攔截器鏈:" + filterChainDefinitionMap) shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap) return shiroFilterFactoryBean } //SecurityManager 是 Shiro 架構的核心,通過它來連結Realm和使用者(文件中稱之為Subject.) @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager() //設定realm. securityManager.setAuthenticator(modularRealmAuthenticator()) List<Realm> realms = new ArrayList<>() //新增多個Realm realms.add(adminShiroRealm()) realms.add(teacherShiroRealm()) realms.add(studentShiroRealm()) securityManager.setRealms(realms) return securityManager } /** * 系統自帶的Realm管理,主要針對多realm * */ @Bean public ModularRealmAuthenticator modularRealmAuthenticator(){ //自己重寫的ModularRealmAuthenticator UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator() modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()) return modularRealmAuthenticator } @Bean public AdminShiroRealm adminShiroRealm() { AdminShiroRealm adminShiroRealm = new AdminShiroRealm() adminShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設定解密規則 return adminShiroRealm } @Bean public StudentShiroRealm studentShiroRealm() { StudentShiroRealm studentShiroRealm = new StudentShiroRealm() studentShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設定解密規則 return studentShiroRealm } @Bean public TeacherShiroRealm teacherShiroRealm() { TeacherShiroRealm teacherShiroRealm = new TeacherShiroRealm() teacherShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設定解密規則 return teacherShiroRealm } //因為我們的密碼是加過密的,所以,如果要Shiro驗證使用者身份的話,需要告訴它我們用的是md5加密的,並且是加密了兩次。同時我們在自己的Realm中也通過SimpleAuthenticationInfo返回了加密時使用的鹽。這樣Shiro就能順利的解密密碼並驗證使用者名稱和密碼是否正確了。 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher() hashedCredentialsMatcher.setHashAlgorithmName("md5")//雜湊演算法:這裡使用MD5演算法; hashedCredentialsMatcher.setHashIterations(2)//雜湊的次數,比如雜湊兩次,相當於 md5(md5("")); return hashedCredentialsMatcher; } /** * 開啟shiro aop註解支援. * 使用代理方式;所以需要開啟程式碼支援; * 開啟 許可權註解 * Controller才能使用@RequiresPermissions * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor() authorizationAttributeSourceAdvisor.setSecurityManager(securityManager) return authorizationAttributeSourceAdvisor } }

下面是上述程式碼的一些解釋:
這裡寫圖片描述

這裡寫圖片描述

實體類

這裡我只給出了其中的一個許可權實體類:

package com.ciyou.edu.entity

class Permission implements Serializable{
    private static final long serialVersionUID = 1L
    //許可權編號(自增長)
    private Integer permissionId
    //許可權名稱
    private String permissionName
    //許可權字串
    private String permission
    //父編號
    private Integer parentId
    //資源型別
    private Integer type
    //資源路徑
    private String url

    Integer getPermissionId() {
        return permissionId
    }

    void setPermissionId(Integer permissionId) {
        this.permissionId = permissionId
    }

    String getPermissionName() {
        return permissionName
    }

    void setPermissionName(String permissionName) {
        this.permissionName = permissionName
    }

    String getPermission() {
        return permission
    }

    void setPermission(String permission) {
        this.permission = permission
    }

    Integer getParentId() {
        return parentId
    }

    void setParentId(Integer parentId) {
        this.parentId = parentId
    }

    String getUrl() {
        return url
    }

    void setUrl(String url) {
        this.url = url
    }

    Integer getType() {
        return type
    }

    void setType(Integer type) {
        this.type = type
    }


    @Override
    public String toString() {
        return "Permission{" +
                "permissionId=" + permissionId +
                ", permissionName='" + permissionName + '\'' +
                ", permission='" + permission + '\'' +
                ", parentId=" + parentId +
                ", type=" + type +
                ", url='" + url + '\'' +
                '}';
    }
}

動態修改許可權

在專案中,可以對許可權進行增刪改操作,然而許可權在shiroConfig中已經配置死了,所以我們要在我們的專案中手動更新,下面給出的是我專案中的程式碼:

package com.ciyou.edu.service.impl

import com.ciyou.edu.entity.Permission
import com.ciyou.edu.mapper.PermissionMapper
import com.ciyou.edu.service.ShiroService
import org.apache.shiro.spring.web.ShiroFilterFactoryBean
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver
import org.apache.shiro.web.servlet.AbstractShiroFilter
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class ShiroServiceImpl implements ShiroService{

    private static final Logger logger = LoggerFactory.getLogger(ShiroServiceImpl.class)
    @Autowired
    private ShiroFilterFactoryBean shiroFilterFactoryBean
    @Autowired
    private PermissionMapper permissionMapper

    /**
     * 初始化許可權
     */
    public Map<String, String> loadFilterChainDefinitions() {
        // 許可權控制map.從資料庫獲取
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>()
        filterChainDefinitionMap.put("/logout", "logout")
        filterChainDefinitionMap.put("/favicon.ico", "anon")
        filterChainDefinitionMap.put("/adminLogin", "anon")
        filterChainDefinitionMap.put("/teacherLogin", "anon")
        //允許訪問靜態資源
        filterChainDefinitionMap.put("/static/**", "anon")
        List<Permission> list = permissionMapper?.findAllPermission()

        for (Permission permission : list) {
            filterChainDefinitionMap.put(permission?.getUrl(),"perms["+ permission?.getPermission() +"]")
        }
        filterChainDefinitionMap.put("/admin/**","roles[Admin]")
        filterChainDefinitionMap.put("/teacher/**","roles[Teacher]")
        filterChainDefinitionMap.put("/student/**","roles[Student]")
        //   過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊
        filterChainDefinitionMap.put("/**", "authc")
        return filterChainDefinitionMap
    }

    /**
     * 重新載入許可權
     */
    @Override
    public void updatePermission() {

        synchronized (shiroFilterFactoryBean) {

            AbstractShiroFilter shiroFilter = null
            try {
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean?.getObject()
            } catch (Exception e) {
                logger.info("重新載入許可權失敗:" + e.getMessage())
                throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!")
            }
            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter?.getFilterChainResolver()
            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver?.getFilterChainManager()

            // 清空老的許可權控制
            manager?.getFilterChains()?.clear()

            shiroFilterFactoryBean?.getFilterChainDefinitionMap()?.clear()
            shiroFilterFactoryBean?.setFilterChainDefinitionMap(loadFilterChainDefinitions())
            // 重新構建生成
            Map<String, String> chains = shiroFilterFactoryBean?.getFilterChainDefinitionMap()
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry?.getKey()
                String chainDefinition = entry?.getValue()?.trim()?.replace(" ", "")
                manager.createChain(url, chainDefinition)
            }

            logger.info("更新許可權成功!!")
        }
    }
}

在Controller中呼叫
這裡寫圖片描述

到此為止shiro的整合就基本完成了。

本文是根據我一個專案中給出的配置,具體的專案原始碼可以檢視:CIYOU