Shiro【实战进阶】

一、前言

Shiro【项目实战】中有总结过 Shiro 的基本的配置,本文则总结通过整合 Redis 实现缓存和会话存储。

三、整合 Redis 实现缓存

(一)为什么要使用整合 Redis

因为在授权的时候,每执行一次操作都要重新去数据库查询相关权限,而权限几乎不会修改,

所以,针对这种修改少但是查询次数多的场景,非常适合使用缓存。

Shiro 中默认的缓存都是保存在应用程序的内存中的,如果程序重启,那么缓存的内容就会丢失。

(二)整合方案

方案一: 不需要通过整合CacheManager的方式,只需要在业务代码中查询时,先查询 Redis 中是否有。
​ 有则返回;没有再查询,然后进行缓存。
方案二: 使用整合CacheManager的方式。实战项目中就是使用的该种方式。

(三)步骤
  1. 引入整合的jar包
  2. 配置 RedisManager
  3. 配置 CacheManager
  4. 在 SecurityManager 中加入 CacheManager
  5. 注意点:在自定义的Realm中封装数据时有两个注意点(在CustomRealm中有写)
(四)实现代码

1)引入整合的jar包:

<!-- shiro+redis缓存插件 -->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.1.0</version>
</dependency>

2)配置 RedisManager:

public RedisManager getRedisManager(){
    RedisManager redisManager = new RedisManager();
    redisManager.setHost("127.0.0.1");
    redisManager.setPort(6379);
    return redisManager;
}

3)配置 CacheManager:

public RedisCacheManager cacheManager(){
    RedisCacheManager redisCacheManager = new RedisCacheManager();
    redisCacheManager.setRedisManager(getRedisManager());

    //设置过期时间,单位是秒,20s
    redisCacheManager.setExpire(20);
    return redisCacheManager;
}

4)在 SecurityManager 中加入CacheManager:

@Bean
    public SecurityManager getSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //如果不是前后端分离,则不必设置下面的sessionManager
        securityManager.setSessionManager(getSessionManager());

        //使用自定义的cacheManager
        securityManager.setCacheManager(cacheManager());

        //设置realm(推荐放到最后,不然某些情况会不生效)
        securityManager.setRealm(getRealm());

        return securityManager;
    }

5)注意点:

/**
 * 从数据库中查询账号和权限相关的数据
 */
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证开始");

        // 获取用户输入的账号
        String username = (String)authenticationToken.getPrincipal();

        // 根据账号从数据库中查询用户相关信息
        User userDb = userService.findAllUserInfoByUsername(username);

        // 若账号不存在(密码不存在其实也是反映账号不存在)则直接返回null
        String pwd = userDb.getPassword();
        if(pwd == null || "".equals(pwd)){
            return null;
        }

        // 将数据库中查询出来的数据封装到 SimpleAuthenticationInfo 对象中返回
        // 真正的认证过程是由 shiro 自动帮我们进行的,可以从源码中看到的。
        // 我们要做的其实就只是将数据从数据库中查询出来并封装到对象中返回
		//return new SimpleAuthenticationInfo(username, pwd, this.getClass().getName());

        // 整合Redis与CacheManager需要注意的点01:需要传递整个User对象.
        // 因为Redis会自动从对象中获取唯一的属性作为key,此处的key就是User对象中的id属性
        return new SimpleAuthenticationInfo(userDb, pwd, this.getClass().getName());
    }

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权开始");

        // 获取用户输入的账号
		//String username = (String)principalCollection.getPrimaryPrincipal();
        // 整合Redis与CacheManager需要注意的点02:因为在认证的时候是封装的整个User对象
        // 所以在授权的时候取出来也是一个User对象
        User user = (User)principalCollection.getPrimaryPrincipal();

        // 根据账号从数据库中查询用户相关信息
        User userDb = userService.findAllUserInfoByUsername(user.getUsername());

        // 以下的操作是查询并封装用户和权限相关信息
        Set<String> stringRoleList = new HashSet<>();
        Set<String> stringPermissionList = new HashSet<>();
        List<Role> roleList = userDb.getRoleList();
        for (Role role : roleList) {
            stringRoleList.add(role.getName());
            for (Permission permission : role.getPermissionList()) {
                if (permission != null){
                    stringPermissionList.add(permission.getName());
                }
            }
        }

        // 将数据库中查询出来的数据封装到 SimpleAuthorizationInfo 对象中返回
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(stringRoleList);
        simpleAuthorizationInfo.setStringPermissions(stringPermissionList);
        return simpleAuthorizationInfo;
    }
}

四、整合 Redis 实现会话存储

(一)为什么要整合 Redis

session都是保存在应用程序的内存中的,如果程序重启,那么保存的session就会丢失,会极大的降低用户的体验度。并且,如果是分布式的话,session也无法共享。

(二)整合方案

通过 RedisSessionDAO 的方式进行整合。

(三)步骤
  1. 导入整合jar包;

  2. 配置 RedisManager

  3. 配置 RedisSessionDAO

  4. 在 SessionManager 中加入 RedisSessionDAO

  5. 在 SecurityManager 中加入 SessionManager

  6. 注意点

(四)实现代码
  1. 导入整合jar包;
<!-- shiro+redis缓存插件 -->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.1.0</version>
</dependency>
  1. 配置 RedisManager :
public RedisManager getRedisManager(){
    RedisManager redisManager = new RedisManager();
    redisManager.setHost("127.0.0.1");
    redisManager.setPort(6379);
    return redisManager;
}
  1. 配置 RedisSessionDAO :
public RedisSessionDAO redisSessionDAO(){
    RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
    redisSessionDAO.setRedisManager(getRedisManager());
    return redisSessionDAO;
}
  1. 在 SessionManager 中加入 RedisSessionDAO :
@Bean
    public SessionManager getSessionManager(){
        CustomSessionManager sessionManager = new CustomSessionManager();

        // 设置 session 过期时间,单位为毫秒,默认时间为30分钟
		essionManager.setGlobalSessionTimeout(1000*60*20);

        //配置session持久化
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }
  1. 在 SecurityManager 中加入 SessionManager :
@Bean
public SecurityManager getSecurityManager(){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

    //如果不是前后端分离,则不必设置下面的sessionManager
    securityManager.setSessionManager(getSessionManager());

    //使用自定义的cacheManager
    securityManager.setCacheManager(cacheManager());

    //设置realm(推荐放到最后,不然某些情况会不生效)
    securityManager.setRealm(getRealm());
    return securityManager;
}
  1. 注意点:
  1. 因为涉及到保存对象,所以User、Role、Permission这些类都需要实现Serializable接口(非常重要)
  2. logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的sessionId

五、项目源代码

链接: https://pan.baidu.com/s/1fKGMSvEmXBItHD6U_pduhg 提取码: jhwy

Java新手,若有错误,欢迎指正!

原文地址:https://www.cnblogs.com/Java-biao/p/14480795.html