声明:本证项目基于《Java-Shiro(六):Shiro Realm讲解(三)Realm的自定义及应用》构建项目为基础。

本文代码:https://github.com/478632418/springmv_without_web_xml/tree/master/mybaits-test-dynamic-sql-02

Shiro内部提供了对认证信息和授权信息的缓存,但是shiro默认是关闭认证信息缓存,对于授权信息的缓存shiro默认开启的。一般情况下,使用shiro缓存时,只需要关注授权信息缓存,因为认证信息只是一次验证查询,而授权信息需要在每次认证都会执行(访问量大),且一般情况下授权的数据量大。

但是,当用户信息被修改时,我们希望理解看到认证信息也被同步时,需要关注认证信息清空同步问题。

Shiro授权缓存的使用

配置

Java-Shiro(七):Shiro认证、授权信息缓存

 

在授权信息缓存的方案包含以下三种:

1)使用Ehcache(系统混合缓存方案);
2)使用本地内存缓存方案;
3)自定义CacheManager(比如Redis用来作为缓存)。

1)使用Ehcache(系统混合缓存方案)

1)在pom.xml中引入shiro-ehcache依赖;

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>

2)在applicationContext-shiro.xml中添加ehcache配置并被securityManager的cacheManage引用:

    <!--cacheManager-->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>
    
    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"></property>
        <property name="cacheManager" ref="cacheManager"></property>
    </bean>

3)在src/main/resources下添加shiro-ehcache.xml ehcache配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- 数据缓存地址,如,F:/develop/ehcache -->
    <diskStore path="java.io.tmpdir/shiro-spring-sample"/>
    <!-- 
    name="authorizationCache" //缓存名称
    maxEntriesLocalHeap="2000" //缓存最大条目数
    eternal="false" //对象是否永久有效,true则timeout失效
    timeToIdleSeconds="3600" //对象在失效前的闲置时间(单位:s),仅eternal=false时有效;默认为0,即可闲置时间无穷大。
    timeToLiveSeconds="0" //缓存数据的生成时间(单位:s),介于创建时间和失效时间之间;仅eternal=false有效;默认为0,即对象存活时间无穷大。
    overflowToDisk="false" //内存中对象数量达到maxElementInMemory时,是否将对象写到磁盘
    diskSpoolBufferSizeMB:设置diskStore磁盘缓存的缓存区大小,默认30MB。每个Cache都应该有自己的一个缓存区。
    maxElementOnDisk:磁盘最大缓存个数。
    diskPersistent:是否缓存虚拟机重启期数据,默认false。
    diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔,默认120s。
    memoryStoreEvictionPolicy:达到maxElementInMemory时,Ehcache将会根据此策略去清理内存,默认策略是LRU(最近最少使用),可设为FIFO(先进先出)或LFU(较少使用)。
    clearOnFlush: 内存数量最大时是否清除。
     -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
    
    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           eternal="true"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>

    <cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
           maxElementsInMemory="100"
           eternal="false"
           timeToLiveSeconds="600"
           overflowToDisk="false"/>

</ehcache>

备注:ehcache中cache标签属性意义:

name="authorizationCache" //缓存名称
maxEntriesLocalHeap="2000" //缓存最大条目数
eternal="false" //对象是否永久有效,true则timeout失效
timeToIdleSeconds="3600" //对象在失效前的闲置时间(单位:s),仅eternal=false时有效;默认为0,即可闲置时间无穷大。
timeToLiveSeconds="0" //缓存数据的生成时间(单位:s),介于创建时间和失效时间之间;仅eternal=false有效;默认为0,即对象存活时间无穷大。
overflowToDisk="false" //内存中对象数量达到maxElementInMemory时,是否将对象写到磁盘
diskSpoolBufferSizeMB:设置diskStore磁盘缓存的缓存区大小,默认30MB。每个Cache都应该有自己的一个缓存区。
maxElementOnDisk:磁盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据,默认false。
diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔,默认120s。
memoryStoreEvictionPolicy:达到maxElementInMemory时,Ehcache将会根据此策略去清理内存,默认策略是LRU(最近最少使用),可设为FIFO(先进先出)或LFU(较少使用)。
clearOnFlush: 内存数量最大时是否清除。

2)使用本地内存缓存方案

applicationContext-shiro.xml中配置cacheManager,该cacheManager并被securityManager引用。

    <!-- // 采用本地内存方式缓存 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
        
    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"></property>
        <property name="cacheManager" ref="cacheManager"></property>
     </bean>

其他不需要配置即可。

3)自定义CacheManager

比如Redis用来作为缓存。

1)需要添加redis依赖到pom.xml

        <!-- redis依赖包 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

2)在src/main/resources下新建applicaitonContext-redis.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
      <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:jedis.properties" ignore-unresolvable="true" />
    
    <!-- 连接池配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大连接数 -->
        <property name="maxTotal" value="${redis.maxTotal}" />
        <!-- 最大空闲连接数 -->
        <property name="maxIdle" value="${redis.maxIdle}" />
        <!-- 每次释放连接的最大数目 -->
        <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />
        <!-- 释放连接的扫描间隔(毫秒) -->
        <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />
        <!-- 连接最小空闲时间 -->
        <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />
        <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
        <property name="softMinEvictableIdleTimeMillis" value="${redis.softMinEvictableIdleTimeMillis}" />
        <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
        <!-- 在获取连接的时候检查有效性, 默认false -->
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />
        <!-- 在空闲时检查有效性, 默认false -->
        <property name="testWhileIdle" value="${redis.testWhileIdle}" />
        <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
        <property name="blockWhenExhausted" value="${redis.blockWhenExhausted}" />
    </bean>
 
    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg name="host" value="${redis.host}"></constructor-arg>
        <constructor-arg name="port" value="${redis.port}"></constructor-arg>
        <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
    </bean>

    <!-- 需要密码 -->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:host-name="${redis.host}"
          p:port="${redis.port}"
          p:password="${redis.pass}"
          p:pool-config-ref="jedisPoolConfig"/>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory"     ref="connectionFactory" />
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>
    </bean>
</beans>

配置中需要依赖资源文件jedis.properties

redis.maxTotal=2000
redis.maxIdle=50
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.minEvictableIdleTimeMillis=1800000
redis.softMinEvictableIdleTimeMillis=10000
redis.maxWaitMillis=15000
redis.testOnBorrow=false
redis.testWhileIdle=false
redis.testOnReturn=false
redis.blockWhenExhausted=true
redis.host=127.0.0.1
redis.port=6379
redis.pass=

3)修改web.xml,ContextLoaderListener加载文件添加applicationContext-redis.xml,修改后为:

    <!-- 加载spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext-redis.xml,
            classpath:applicationContext-shiro.xml,
            classpath:applicationContext-mybatis.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

4)新建redis缓存管理类RedisCache.java和RedisCacheManager.java

RedisCache.java

public class RedisCache<K, V> implements Cache<K, V> {
    @Autowired
    private RedisTemplate redisTemplate;
    private Logger logger;
    private String keyPrefix;

    public String getKeyPrefix() {
        return this.keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    public RedisCache(RedisTemplate redisTemplate) {
        this.logger = LoggerFactory.getLogger(this.getClass());
        this.keyPrefix = "shiro_redis_cache:";
        this.redisTemplate = redisTemplate;
    }

    public RedisCache(RedisTemplate redisTemplate, String prefix) {
        this(redisTemplate);
        this.keyPrefix = prefix;
    }

    /**
     * 获得byte[]型的key
     *
     * @param key
     * @return
     */
    private byte[] getByteKey(Object key) {
        if (key instanceof String) {
            String preKey = this.keyPrefix + key;
            return preKey.getBytes();
        } else {
            return SerializeUtils.serialize((Serializable) key);
        }
    }

    private RedisConnection getRedisConnect() {
        return redisTemplate.getConnectionFactory().getConnection();
    }

    @Override
    public Object get(Object key) throws CacheException {
        byte[] bytes = getByteKey(key);
        byte[] value = getRedisConnect().get(bytes);
        if (value == null) {
            return null;
        }
        return SerializeUtils.deserialize(value);
    }

    /**
     * 将shiro的缓存保存到redis中
     */
    @Override
    public Object put(Object key, Object value) throws CacheException {
        RedisConnection redisConnection = getRedisConnect();
        byte[] bytesKey = getByteKey(key);
        byte[] bytesValue = SerializeUtils.serialize((Serializable) value);

        redisConnection.set(bytesKey, bytesValue);

        byte[] bytes = redisConnection.get(getByteKey(key));
        Object object = SerializeUtils.deserialize(bytes);

        return object;

    }

    @Override
    public Object remove(Object key) throws CacheException {
        RedisConnection redisConnection = getRedisConnect();

        byte[] bytes = redisConnection.get(getByteKey(key));

        redisConnection.del(getByteKey(key));

        return SerializeUtils.deserialize(bytes);
    }

    /**
     * 清空所有缓存
     */
    @Override
    public void clear() throws CacheException {
        RedisConnection redisConnection = getRedisConnect();
        redisConnection.flushDb();
    }

    /**
     * 缓存的个数
     */
    @Override
    public int size() {
        RedisConnection redisConnection = getRedisConnect();
        Long size = redisConnection.dbSize();
        return size.intValue();
    }

    /**
     * 获取所有的key
     */
    @Override
    public Set keys() {
        RedisConnection redisConnection = getRedisConnect();
        Set<byte[]> keys = redisConnection.keys(new String("*").getBytes());
        Set<Object> set = new HashSet<Object>();
        for (byte[] bs : keys) {
            set.add(SerializeUtils.deserialize(bs));
        }
        return set;
    }


    /**
     * 获取所有的value
     */
    @Override
    public Collection values() {
        RedisConnection redisConnection = getRedisConnect();
        Set keys = this.keys();

        List<Object> values = new ArrayList<Object>();
        for (Object key : keys) {
            byte[] bytes = redisConnection.get(getByteKey(key));
            values.add(SerializeUtils.deserialize(bytes));
        }
        return values;
    }

}
View Code

相关文章:

  • 2022-12-23
  • 2021-07-16
  • 2018-11-20
  • 2022-12-23
  • 2021-08-13
  • 2021-08-27
  • 2021-10-30
猜你喜欢
  • 2022-12-23
  • 2021-11-14
  • 2022-12-23
  • 2022-01-13
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案