shiro 學習筆記


1. 權限管理

1.1 什么是權限管理?

權限管理實現對用戶訪問系統的控制,按照安全規則或者安全策略,可以控制用戶只能訪問自己被授權的資源

權限管理包括用戶身份認證和授權兩部分,簡稱認證授權

1.2 什么是身份認證?

身份認證就是判斷一個用戶是否為合法用戶的處理過程,最常用的方式就是通過核對用戶輸入和用戶名和口令是否與系統中存儲的一致,來判斷用戶身份是否正確

1.3 什么是授權?

授權,即訪問控制,控制誰能訪問哪些資源,主體進行身份認證后需要分配權限方可訪問系統資源,對於某些資源沒有權限是無法訪問的


2. 什么是 shiro?

shiro 是一個功能強大且易於使用的 Java 安全框架,能夠完成身份認證、授權、加密和會話管理等功能

2.1 shiro 的核心架構

  • Subject

    主體,外部應用與 subject 進行交互,subject 記錄了當前操作用戶,將用戶的概念理解為當前操作的主體,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序。Subject 在 shiro 中是一個接口,接口中定義了很多認證授相關的方法,外部程序通過 subject 進行認證授,而 subject 是通過 SecurityManager 安全管理器進行認證授權

  • SecurityManager

    安全管理器,對全部的 subject 進行安全管理,是 shiro 的核心。通過 SecurityManager 可以完成 subject 的認證、授權等,實質上 SecurityManager 是通過 Authenticator 進行認證,通過 Authorizer 進行授權,通過 SessionManager 進行會話管理。SecurityManager 是一個接口,繼承了 Authenticator,Authorizer,SessionManager 這三個接口

  • Authenticator

    認證器,對用戶身份進行認證,Authenticator 是一個接口,shiro 提供 ModularRealmAuthenticator 實現類,通過 ModularRealmAuthenticator 基本上可以滿足大多數需求,也可以自定義認證器

  • Authorizer

    授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限

  • Realm

    領域,相當於 datasource 數據源,securityManager 進行安全認證需要通過 Realm 獲取用戶權限數據,比如:如果用戶身份數據在數據庫,那么 realm 就需要從數據庫獲取用戶身份信息

  • SessionManager

    會話管理,shiro 框架定義了一套會話管理,它不依賴 web 容器的 session,所以 shiro 可以使用在非 web 應用上,也可以將分布式應用的會話集中在一點管理,此特性可使它實現單點登錄

  • SessionDAO

    會話 dao,是對 session 會話操作的一套接口,比如要將 session 存儲到數據庫,可以通過 jdbc 將會話存儲到數據庫

  • CacheManager

    CacheManager 即緩存管理,將用戶權限數據存儲在緩存,這樣可以提高性能

  • Cryptography

    密碼管理,shiro 提供了一套加解密的組件,方便開發,比如提供常用的散列、加解密等功能


3. shiro 中的認證

3.1 認證

身份認證,就是判斷一個用戶是否為合法用戶的處理過程。最常用的身份認證方式是系統通過核對用戶輸入的用戶名和口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確

3.2 shiro 認證中的關鍵對象

  • Subject:主體

    訪問系統的用戶,主體可以是用戶、程序等,進行認證的都稱為主體

  • Principal:身份信息

    是主體(subject)進行身份認證的標識,標識必須具有唯一性, 如用戶名、手機號、郵箱地址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)

  • credential:憑證信息

    是只有主體自己知道的安全信息,如密碼、證書等

3.3 認證流程

3.4 認證開發

3.4.1 引入依賴

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.5.3</version>
</dependency>

3.4.2 創建配置文件

該配置文件用來書寫系統中相關的權限數據,主要用於學習使用

[users]
xiaochen=123
zhangsan=456

3.4.3 開發認證代碼

public class TestAuthenticator {

    public static void main(String[] args) {
        // 1.創建安全管理器對象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.給安全管理器設置 realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        // 3.給 SecurityUtils 全局安全工具類設置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.獲取認證主體
        Subject subject = SecurityUtils.getSubject();
        // 5.創建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        // 6.認證
        try {
            System.out.println("認證狀態:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態:" + subject.isAuthenticated());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.5 自定義 Realm

自定義 Realm 的實現,即是將認證/授權數據來源轉為數據庫的實現

public class TestCustomerRealmAuthenticator {

    public static void main(String[] args) {
        // 1.創建安全管理器對象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.設置自定義realm
        securityManager.setRealm(new CustomerRealm());
        // 3.給 SecurityUtils 全局安全工具類設置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.獲取認證主體
        Subject subject = SecurityUtils.getSubject();
        // 5.創建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        // 6.認證
        try {
            System.out.println("認證狀態:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態:" + subject.isAuthenticated());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定義 Realm 代碼實現

public class CustomerRealm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 1. 在token中獲取用戶名
        String principal = (String) authenticationToken.getPrincipal();
        // 2. 查詢數據庫,此處模擬數據庫數據
        if ("xiaochen".equals(principal)) {
            // 參數1:正確的用戶名
            // 參數2:正確的密碼
            // 參數3:提供當前realm的名字
            SimpleAuthenticationInfo simpleAuthenticationInfo
                    = new SimpleAuthenticationInfo("xianchen", "123", this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

3.6 明文加密

實際使用時,我們不可能把用戶密碼以明文形式顯示,需要做加密處理

通常的加密方式是使用 md5 + salt + hash 散列的形式,校驗過程:保存鹽和散列后的值,在 shiro 完成密碼校驗

下面是使用 shiro 提供的 api 完成加密代碼示例

public class TestShiroMD5 {

    public static void main(String[] args) {
        // 1. 使用md5加密
        // 參數1是明文密碼
        Md5Hash md5Hash1 = new Md5Hash("123");
        // 打印加密后的密文
        // 結果:202cb962ac59075b964b07152d234b70
        System.out.println(md5Hash1.toHex());
        // 2. 使用md5+salt加密
        Md5Hash md5Hash2 = new Md5Hash("123", "X0*7ps");
        // 8a83592a02263bfe6752b2b5b03a4799
        System.out.println(md5Hash2.toHex());
        // 3. 使用md5+salt+hash散列加密
        Md5Hash md5Hash3 = new Md5Hash("123", "X0*7ps", 1024);
        // e4f9bf3e0c58f045e62c23c533fcf633
        System.out.println(md5Hash3.toHex());
    }
}

自定義 CustomerMd5Realm

public class CustomerMd5Realm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 1. 在token中獲取用戶名
        String principal = (String) authenticationToken.getPrincipal();
        // 2. 查詢數據庫,此處模擬數據庫數據
        if ("xiaochen".equals(principal)) {
            // 參數1:正確的用戶名
            // 參數2:正確的密碼
            // 參數3:提供當前realm的名字
            // md5
            // return new SimpleAuthenticationInfo(principal, "202cb962ac59075b964b07152d234b70", this.getName());
            // md5+salt
            /*return new SimpleAuthenticationInfo(principal, "8a83592a02263bfe6752b2b5b03a4799", ByteSource.Util.bytes("X0*7ps"), this.getName());*/
        }
        return null;
    }
}

校驗流程

public class TestCustomerMd5RealmAuthenticator {

    public static void main(String[] args) {
        // 1.創建安全管理器對象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2.設置自定義realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用MD5加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 散列次數
        hashedCredentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        securityManager.setRealm(realm);
        // 3.給 SecurityUtils 全局安全工具類設置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4.獲取認證主體
        Subject subject = SecurityUtils.getSubject();
        // 5.創建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        // 6.認證
        try {
            System.out.println("認證狀態:" + subject.isAuthenticated());
            subject.login(token);
            System.out.println("認證狀態:" + subject.isAuthenticated());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. shiro 中的授權

4.1 授權

授權,即訪問控制,控制誰能訪問哪些資源。主體進行身份認證后需要分配權限方可訪問系統的資源,對於某些資源沒有權限是無法訪問的

4.2 關鍵對象

授權可簡單理解為 who 對 what(which) 進行 How 操作:

  • Who,即主體(Subject),主體需要訪問系統中的資源
  • What,即資源(Resource),如系統菜單、頁面、按鈕、類方法、系統商品信息等。資源包括資源類型和資源實例,比如商品信息為資源類型,類型為 t01 的商品為資源實例,編號為 001 的商品信息也屬於資源實例
  • How,權限/許可(Permission),規定了主體對資源的操作許可,權限離開資源沒有意義,如用戶查詢權限、用戶添加權限、某個類方法的調用權限、編號為 001 用戶的修改權限等,通過權限可知道主體對哪些資源都有哪些操作許可

4.3 授權方式

基於角色的訪問控制,以角色為中心進行訪問控制

if(subject.hasRole("admin")){
   //操作什么資源
}

基於資源的訪問控制,以資源為中心進行訪問控制

if(subject.isPermission("user:update:01")){ //資源實例
	//對01用戶進行修改
}
if(subject.isPermission("user:update:*")){  //資源類型
	//對01用戶進行修改
}

4.4 權限字符串

權限字符串的規則是:資源標識符:操作:資源實例標識符,意思是對哪個資源的哪個實例具有什么操作,: 是資源/操作/實例的分割符,權限字符串也可以使用 * 通配符

例子:

  • 用戶創建權限:user:create,或 user:create:*
  • 用戶修改實例 001 的權限:user:update:001
  • 用戶實例 001 的所有權限:user:*:001

4.5 授權編程實現

在之前 md5 加密的基礎上,實現授權操作

自定義 CustomerMd5Realm

public class CustomerMd5Realm extends AuthorizingRealm {

    // 授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 獲取身份信息
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        // 根據身份信息(用戶名)從數據庫獲取當前用戶的角色以及權限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 將數據庫中查詢的角色信息賦值給權限對象
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");
        // 將數據庫中查詢的權限信息賦值給權限對象
        simpleAuthorizationInfo.addStringPermission("user:*:01");
        simpleAuthorizationInfo.addStringPermission("product:create:02");
        return simpleAuthorizationInfo;
    }

    ...
}

授權邏輯

public class TestCustomerMd5RealmAuthenticator {

    public static void main(String[] args) {
        
        ...

        // 7. 認證用戶進行授權
        if (subject.isAuthenticated()) {
            // 7.1 基於角色權限控制
            boolean hasRole = subject.hasRole("admin");
            System.out.println("角色校驗:" + hasRole);
            // 7.2 基於多角色權限控制
            boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("admin", "user"));
            System.out.println("多角色校驗:" + hasAllRoles);
            // 7.3 是否具有其中一個角色
            boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
            for (boolean aBoolean : booleans) {
                System.out.println(aBoolean);
            }
            // 7.4 基於權限字符串的訪問控制
            boolean permitted = subject.isPermitted("user:*:01");
            System.out.println("資源權限校驗:" + permitted);
            // 7.5 分布具有哪些資源權限
            boolean[] permitted1 = subject.isPermitted("user:*:01", "order:*:10");
            for (boolean b : permitted1) {
                System.out.println(b);
            }
            // 7.6 同時具有哪些資源權限
            boolean permittedAll = subject.isPermittedAll("user:*:01", "product:*");
            System.out.println("多資源權限校驗:" + permittedAll);
        }
    }
}

5. shiro 整合 SpringBoot

shiro 整合 SpringBoot 的思路如圖所示:

引入 shiro 整合 SpringBoot 依賴

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>

5.1 認證

配置 shiro

@Configuration
public class ShiroConfig {

    // 1.創建shiroFilter,負責攔截所有請求
    @Bean
    public ShiroFilterFactoryBean getShiroFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 給filter設置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        // 配置系統受限資源
        HashMap<String, String> map = new HashMap<>();
        map.put("/user/login", "anon");  // anon設置為公共資源
        map.put("/user/register", "anon");
        map.put("/register.jsp", "anon");
        map.put("/**", "authc"); // authc表示請求這個資源需要認證和授權
        // 默認認證界面路徑
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    // 2.創建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") Realm realm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 給安全管理器設置Realm
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }

    // 3.創建自定義realm
    @Bean(name = "realm")
    public Realm getRealm() {
        CustomerRealm customerRealm = new CustomerRealm();
        // 修改憑證校驗匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 設置加密算法為md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        // 設置散列次數
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}
public class CustomerRealm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        UserSev userSev = (UserSev) ApplicationContextUtils.getBean("userSev");
        User user = userSev.findByUsername(principal);
        if (user != null) {
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()), this.getName());
        }
        return null;
    }
}

shiro 提供了多個默認的過濾器,我們可以用這些過濾器來控制指定 url 的權限

配置縮寫 對應的過濾器 功能
anon AnonymousFilter 指定url可以匿名訪問
authc FormAuthenticationFilter 指定url需要form表單登錄,默認會從請求中獲取usernamepassword,rememberMe等參數並嘗試登錄,如果登錄不了就會跳轉到loginUrl配置的路徑。我們也可以用這個過濾器做默認的登錄邏輯,但是一般都是我們自己在控制器寫登錄邏輯的,自己寫的話出錯返回的信息都可以定制嘛。
authcBasic BasicHttpAuthenticationFilter 指定url需要basic登錄
logout LogoutFilter 登出過濾器,配置指定url就可以實現退出功能,非常方便
noSessionCreation NoSessionCreationFilter 禁止創建會話
perms PermissionsAuthorizationFilter 需要指定權限才能訪問
port PortFilter 需要指定端口才能訪問
rest HttpMethodPermissionFilter 將http請求方法轉化成相應的動詞來構造一個權限字符串,這個感覺意義不大,有興趣自己看源碼的注釋
roles RolesAuthorizationFilter 需要指定角色才能訪問
ssl SslFilter 需要https請求才能訪問
user UserFilter 需要已登錄或“記住我”的用戶才能訪問

模擬認證、注冊和退出過程

@Controller
@RequestMapping("user")
public class UserCon {

    @Autowired
    private UserSev userSev;

    @RequestMapping("logout")
    public String logout(String username, String password) {
        // 獲取主體對象
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }

    @RequestMapping("login")
    public String login(String username, String password) {
        // 獲取主體對象
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username, password));
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            System.out.println("用戶名錯誤");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密碼錯誤");
        }
        return "redirect:/index.jsp";
    }

    @RequestMapping("register")
    public String register(User user) {
        try {
            userSev.register(user);
        } catch (Exception e) {
            return "redirect:/register.jsp";
        }
        return "redirect:/login.jsp";
    }
}
@Service
public class UserSev {

    @Autowired
    private UserDao userDao;

    public void register(User user) {
        // 處理業務調用dao
        // 明文密碼進行 md5 + salt + hash散列
        String salt = SaltUtils.getSalt();
        user.setSalt(salt);
        Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
        user.setPassword(md5Hash.toHex());
        userDao.save(user);
    }

    public User findByUsername(String username) {
        return userDao.findByUserName(username);
    }
}

5.2 授權

第一種方式,通過頁面標簽授權

<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:hasAnyRoles name="user,admin">
    <li><a href="">用戶管理</a>
        <ul>
            <shiro:hasPermission name="user:add:*">
                <li><a href="">添加</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:delete:*">
                <li><a href="">刪除</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:update:*">
                <li><a href="">修改</a></li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:find:*">
                <li><a href="">查詢</a></li>
            </shiro:hasPermission>
        </ul>
    </li>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
    <li><a href="">商品管理</a></li>
    <li><a href="">訂單管理</a></li>
    <li><a href="">物流管理</a></li>
</shiro:hasRole>

第二種方式,代碼方式授權

@RequestMapping("save")
public String save(){
  System.out.println("進入方法");
  //獲取主體對象
  Subject subject = SecurityUtils.getSubject();
  //代碼方式
  if (subject.hasRole("admin")) {
    System.out.println("保存訂單!");
  }else{
    System.out.println("無權訪問!");
  }
  //基於權限字符串
  //....
  return "redirect:/index.jsp";
}

第二種方式,注解方式授權

@RequiresRoles(value={"admin","user"})//用來判斷角色  同時具有 admin user
@RequiresPermissions("user:update:01") //用來判斷權限字符串
@RequestMapping("save")
public String save(){
  System.out.println("進入方法");
  return "redirect:/index.jsp";
}

這里只是做個演示,實際開發中,我們需要對授權數據持久化。需要三張表:用戶表、角色表和權限表,用戶表和角色表之間,角色表和權限表之間都是多對多的關系,需要建立一張關系表

修改自定義 Realm

public class CustomerRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取身份信息
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("調用授權驗證: "+primaryPrincipal);
        //根據主身份信息獲取角色 和 權限信息
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        User user = userService.findRolesByUserName(primaryPrincipal);
        //授權角色信息
        if(!CollectionUtils.isEmpty(user.getRoles())){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(role->{
                simpleAuthorizationInfo.addRole(role.getName());
                //權限信息
                List<Perms> perms = userService.findPermsByRoleId(role.getId());
                if(!CollectionUtils.isEmpty(perms)){
                    perms.forEach(perm->{
                        simpleAuthorizationInfo.addStringPermission(perm.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }
}

5.3 shiro 緩存

使用緩存可以減輕 DB 的訪問壓力,從而提高系統的查詢效率

5.3.1 整合 Ehcache

引入 ehcache 依賴

<!--引入shiro和ehcache-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-ehcache</artifactId>
  <version>1.5.3</version>
</dependency>

開啟緩存

@Bean
public Realm getRealm(){
    CustomerRealm customerRealm = new CustomerRealm();
    //修改憑證校驗匹配器
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    //設置加密算法為md5
    credentialsMatcher.setHashAlgorithmName("MD5");
    //設置散列次數
    credentialsMatcher.setHashIterations(1024);
    customerRealm.setCredentialsMatcher(credentialsMatcher);

    //開啟緩存管理
    customerRealm.setCacheManager(new EhCacheManager());
    customerRealm.setCachingEnabled(true);//開啟全局緩存
    customerRealm.setAuthenticationCachingEnabled(true);//認證認證緩存
    customerRealm.setAuthenticationCacheName("authenticationCache");
    customerRealm.setAuthorizationCachingEnabled(true);//開啟授權緩存
    customerRealm.setAuthorizationCacheName("authorizationCache");
    return customerRealm;
}

5.3.2 整合 Redis

引入 redis 依賴

<!--redis整合springboot-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置 redis 連接,啟動 redis 服務

spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=0

ehcache 提供了 EhCacheManager,而 EhCacheManager 實現了 CacheManager 接口,因此我們可以自定義一個 RedisCacheManager

public class RedisCacheManager implements CacheManager {
    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
        System.out.println("緩存名稱: "+cacheName);
        return new RedisCache<K,V>(cacheName);
    }
}

再自定義 Cache 接口實現

public class RedisCache<K,V> implements Cache<K,V> {

    private String cacheName;

    public RedisCache() {
    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public V get(K k) throws CacheException {
        System.out.println("獲取緩存:"+ k);
        return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public V put(K k, V v) throws CacheException {
        System.out.println("設置緩存key: "+k+" value:"+v);
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }

    @Override
    public V remove(K k) throws CacheException {
        return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public v remove(k k) throws CacheException {
        return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }


    // 封裝獲取 redisTemplate
    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

我們還需要對 salt 作序列化實現,由於 shiro 中提供的 SimpleByteSource 實現沒有實現序列化,所以我們需要自定義 salt 序列化實現

 // 自定義 salt 實現,實現序列化接口
 public class MyByteSource extends SimpleByteSource implements Serializable {
     public MyByteSource(String string) {
         super(string);
     }
 }

在 realm 中使用自定義 salt

public class MyByteSource implements ByteSource, Serializable {
  
    private  byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    //加入無參數構造方法實現序列化和反序列化
    public MyByteSource(){

    }

    public MyByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public MyByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public MyByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public MyByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public MyByteSource(File file) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
    }

    public MyByteSource(InputStream stream) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    public byte[] getBytes() {
        return this.bytes;
    }

    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    public String toString() {
        return this.toBase64();
    }

    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource)o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }
}


免責聲明!

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



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