shiro 学习笔记

2年前 (2022) 程序员胖胖胖虎阿
334 0 0

1. 权限管理

1.1 什么是权限管理?

权限管理实现对用户访问系统的控制,按照安全规则或者安全策略,可以控制用户只能访问自己被授权的资源

权限管理包括用户身份认证和授权两部分,简称认证授权

1.2 什么是身份认证?

身份认证就是判断一个用户是否为合法用户的处理过程,最常用的方式就是通过核对用户输入和用户名和口令是否与系统中存储的一致,来判断用户身份是否正确

1.3 什么是授权?

授权,即访问控制,控制谁能访问哪些资源,主体进行身份认证后需要分配权限方可访问系统资源,对于某些资源没有权限是无法访问的

2. 什么是 shiro?

shiro 是一个功能强大且易于使用的 Java 安全框架,能够完成身份认证、授权、加密和会话管理等功能

2.1 shiro 的核心架构

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 认证流程

shiro 学习笔记

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 用户的修改权限等,通过权限可知道主体对哪些资源都有哪些操作许可

shiro 学习笔记

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 学习笔记

引入 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";
}

这里只是做个演示,实际开发中,我们需要对授权数据持久化。需要三张表:用户表、角色表和权限表,用户表和角色表之间,角色表和权限表之间都是多对多的关系,需要建立一张关系表

shiro 学习笔记

修改自定义 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 的访问压力,从而提高系统的查询效率

shiro 学习笔记

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);
        }
    }
}

版权声明:程序员胖胖胖虎阿 发表于 2022年11月4日 下午1:24。
转载请注明:shiro 学习笔记 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...