首頁 > 軟體

springboot整合shiro許可權管理簡單實現

2022-08-05 14:01:54

前言

為了解決專案當中的許可權管理問題,我們一般會選擇引入spring security或者shiro框架來幫助我們更好地更快地構建許可權管理體系。

依賴

首先第一步,我們需要給當前專案引入對應的依賴包。與spring boot整合一般首選starter包。

<!-- shiro許可權管理框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.9.1</version>
</dependency>

設定

無論是spring security還是shiro,兩者都是基於servlet的Filter過濾器機制實現的許可權管理。所以第一步設定我們就需要把對應的Filter給加入進來。

Filter過濾器設定

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    // 設定securityManager,負責許可權驗證的核心事務處理。
    shiroFilterFactoryBean.setSecurityManager(securityManager());
    // 設定過濾器鏈
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    // anon表示該url不需要經過許可權驗證
    filterChainDefinitionMap.put("/static/**", "anon");
    // logout表示使用者登出功能的過濾器;呼叫指定的url會讓已經登陸的使用者退出
    filterChainDefinitionMap.put("/logout", "logout");
    // authc過濾器表示對應的url都需要許可權驗證才能存取
    filterChainDefinitionMap.put("/**", "authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    // 設定使用者登陸的url。呼叫該介面需要傳username和password欄位。
    shiroFilterFactoryBean.setLoginUrl("/login");
    // 登陸成功自動跳轉頁面
    shiroFilterFactoryBean.setSuccessUrl("/index");
    return shiroFilterFactoryBean;
  }

securityManager設定

看上面程式碼,我們就可以分析出,需要把這些Filter過濾器建立出來,除了設定一些需要攔截的url之外,我們還要建立一個非常核心的securityManager,這個才是許可權驗證過程處理的核心。

@Bean
public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm());
    return securityManager;
}

別看securityManager裡面我只設定了Realm,其實securityManager就像一個大領導,壓根不幹活兒,有啥活都派給底下的小弟去幹。

我們來看看securityManager底下到底有哪些小弟;

後續的部落格中會逐一解析這些小弟的作用,今天就先把如何簡單整合shiro講完。

  • SessionManager:管理使用者session;
  • SubjectDao:負責Subject儲存和刪除;
  • CacheManager:負責快取管理;
  • Realm:負責使用者登陸和許可權驗證;
  • RememberMeManager:負責實現remember me功能;
  • EventBus:事件匯流排;
  • SubjectFactory:建立Subject;

Realm設定

下面我們應該要給出如何判斷使用者登陸和鑑定使用者許可權了:

@Bean
public UserRealm userRealm() {
    UserRealm userRealm = new UserRealm();
    userRealm.setCredentialsMatcher(credentialsMatcher());
    return userRealm;
}

因為Realm需要查詢使用者的密碼已經改使用者對應的角色和許可權,所以我們自定義了自己的Realm。

通過繼承AuthorizingRealm:

package com.example.awesomespring.security;
​
import com.example.awesomespring.bo.AccountInfo;
import com.example.awesomespring.service.AccountService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
​
import javax.annotation.Resource;
​
public class UserRealm extends AuthorizingRealm {
  
    @Resource
    private AccountService accountService;
​
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 從authenticationToken中獲取使用者提交的username
        String accountName = (String) authenticationToken.getPrincipal();
        // 通過username查詢到對應的使用者資訊
        AccountInfo accountInfo = accountService.findByAccount(accountName);
        if (accountInfo == null) {
            return null;
        }
        // 取出查詢到的使用者密碼
        String password = accountInfo.getPassword();
        // 取出使用者密碼加密需要用到的鹽值
        String salt = accountInfo.getSalt();
        // 把查詢出來的使用者資訊、密碼、加密鹽值、Realm名稱包裝進SimpleAuthenticationInfo返回
        return new SimpleAuthenticationInfo(accountInfo, password, ByteSource.Util.bytes(salt), getName());
    }
  
    // 這個方法是在使用者登陸成功後,呼叫需要許可權才能存取的介面時才來鑑定許可權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 拿到已經登陸的使用者資訊
        AccountInfo accountInfo = (AccountInfo) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 下面的角色和許可權需要從資料庫中查詢
        // 設定角色
        authorizationInfo.addRole("User");
        // 設定許可權
        authorizationInfo.addStringPermission("User:read");
        // 返回角色和許可權
        return authorizationInfo;
    }
}

到這裡,我們可以看出,Realm就是用來幫助使用者登陸,並且在使用者存取需要許可權的介面時,查詢出使用者的角色和許可權,交給決策器來決定使用者是否登陸成功,鑑權是否通過。

密碼加密

這是一個不容易被注意的點,使用預設的設定時只會簡單的比較輸入的密碼和資料庫查出來的密碼是否一致,這顯然是不符合要求的,因為我們資料庫裡面的密碼是已經加密好了的。

另一個就是我們在建立使用者的時候也是需要使用到同樣的密碼加密手段,所以我們有必要把密碼加密給拎出來處理一下,做一個自定義的加密。

@Bean
public CredentialsMatcher credentialsMatcher() {
    return new PasswordHashedCredentialsMatcher("MD5");
}
​
package com.example.awesomespring.security;
​
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.util.ByteSource;
​
/**
 * @author zouwei
 * @className PasswordHashedCredentialsMatcher
 * @date: 2022/7/31 下午3:43
 * @description:
 */
public class PasswordHashedCredentialsMatcher extends HashedCredentialsMatcher {
​
    public PasswordHashedCredentialsMatcher(String hashAlgorithmName) {
        super(hashAlgorithmName);
        setHashIterations(2);
    }
​
    public String encryptedPassword(String passwordString, String salt) {
        return hashProvidedCredentials(passwordString, ByteSource.Util.bytes(salt), getHashIterations()).toHex();
  }
}

其實我們就是繼承了HashedCredentialsMatcher,在它的基礎上提高了直接針對密碼加密的功能。這樣既能滿足shiro的登陸驗證,又能拿出來用到建立使用者的時候加密使用。

測試

為了驗證我們的設定是否成功,我們需要寫幾個測試介面:

package com.example.awesomespring.controller;
​
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
​
  @GetMapping("/{id}")
  @RequiresPermissions("User:info")
  String getUserInfo(@PathVariable String id) {
    log.info("獲取使用者資訊開始");
    log.info("請求引數 id:{}", id);
    log.info("獲取使用者資訊結束");
    return "getUserInfo";
  }
​
  @GetMapping("/hello")
  String hello() {
    return "hello world!";
  }
​
  @GetMapping("/read")
  @RequiresPermissions("User:read")
  String read() {
    return "read";
  }
}

上面的幾個介面,我們如果直接存取的話,在瀏覽器中,會被重定向到"/login"頁面,因為我們目前沒有這個頁面,所以一旦重定向這個url,說明使用者沒有登陸。

其次我們通過呼叫post請求"/login",提交username和password成功登陸後,頁面會重定向到"/index"頁面,目前我們也是沒有這個頁面的,不過從響應體中可以看到響應碼是302。

當我們在使用者登陸成功後,再存取"/user/read"是能正常存取,並返回結果"read";如果存取"/user/123456"是不行的,頁面會直接報500錯誤。

通過上述測試,我們已經初步完成了shiro的整合

到此這篇關於springboot整合shiro許可權管理簡單實現的文章就介紹到這了,更多相關springboot 整合shiro內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com