Shiro integra redis para el procesamiento de sesiones distribuidas

citado

Este artículo modifica el código que shiro tiene que usar en una sola aplicación , para que shiro use Redis como administrador de sesión para realizar la función de sesión distribuida.

En un entorno de clúster, necesitamos varios servidores en el clúster para poder compartir cachés y sesiones. La solución popular actual es usar la base de datos de Redis como servidor de caché. Shiro no proporciona oficialmente soporte integrado para Redis para el almacenamiento en caché. Hay soporte para Redis en la biblioteca oficial de extensiones de terceros: Shiro-Redis

Sin embargo, han pasado 2 años desde que esta extensión de terceros se actualizó recientemente. Muchas dependencias son demasiado bajas y no se han actualizado. La siguiente dirección es forkuna copia mía que se ha modificado. La versión actual se ha actualizado shiro, jedisy otras Se han actualizado las dependencias. Lo estudiaré más adelante. Cómo usarlo fastjson2como serialización. shiro-redis v3.7

La introducción es casi la misma, y ​​luego comenzamos a implementar el uso de Redis como caché para shiro.

1. Modificar las dependencias de POM

1. Primero elimine todas  shiro-ehcache las dependencias y códigos relevantes en el proyecto, incluido   el contenido en pom.xml, ehcache.xmlyShiroConfig

2. Introducir shiro-redisdependencias

<!-- shiro整合redis:这里直接引用了博主自己升级了有关依赖得jar包 -->
<dependency>
   <groupId>io.github.LiJunYi2</groupId>
   <artifactId>shiro-redis</artifactId>
   <version>3.7</version>
</dependency>
<!-- springboot整合redis -->
 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>com.alibaba.fastjson2</groupId>
   <artifactId>fastjson2</artifactId>
   <version>2.0.9</version>
</dependency>
<dependency>
   <groupId>com.alibaba.fastjson2</groupId>
   <artifactId>fastjson2-extension</artifactId>
   <version>2.0.9</version>
</dependency>

2. Yaml agrega la configuración de Redis

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 6000ms
    lettuce:
      pool:
        max-active: 1000
        max-wait: -1ms
        max-idle: 10
        min-idle: 5

3. Nueva configuración de serialización de Redis

usado aquí esFastJson2

/**
 * @className: FastJson2JsonRedisSerializer
 * @description: Redis使用FastJson序列化
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }


    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

/**
 * @className: RedisConfig
 * @description: redis序列化配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

Cuarto, modifica la configuración de shiro.

@Configuration
public class ShiroConfig {

    /**
     * redis缓存地址
     */
    @Value("${spring.redis.port}")
    private String redisPort;

    /**
     * redis缓存端口
     */
    @Value("${spring.redis.host}")
    private String redisHost;

    /**
     * redis数据库索引
     */
    @Value("${spring.redis.database}")
    private int database;

    /**
     * redis密码
     */
    @Value("${spring.redis.password}")
    private String password;

    /**
     * 登录网址
     */
    @Value("${shiro.user.loginUrl}")
    private String loginUrl;

    /**
     * 成功的url
     */
    @Value("${shiro.user.successUrl}")
    private String successUrl;

    /**
     * 未经授权的url
     */
    @Value("${shiro.user.unauthorizedUrl}")
    private String unauthorizedUrl;


    /**
     * Cache Manager (shiro-redis)
     */
    @Bean
    public RedisCacheManager redisCacheManager()
    {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setPrincipalIdFieldName("loginName");
        redisCacheManager.setValueSerializer(new FstSerializer());
        return redisCacheManager;
    }

    /**
     * RedisManager (shiro-redis)
     */
    @Bean
    public IRedisManager redisManager()
    {
        LettuceRedisManager redisManager = new LettuceRedisManager(redisHost, Convert.toInt(redisPort));
        redisManager.setDatabase(database);
        redisManager.setTimeout(30 * 60);
        return redisManager;
    }

    /**
     *  自定义Realm
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm shiroRealm = new MyShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        shiroRealm.setCacheManager(redisCacheManager());
        return shiroRealm;
    }

    /**
     * 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码; )
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }

    /**
     * 退出过滤器
     */
    public LogoutFilter logoutFilter(){
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setLoginUrl(loginUrl);
        logoutFilter.setCacheManager(redisCacheManager());
        return logoutFilter;
    }

    /**
     * RedisSessionDAO (shiro-redis)
     */
    @Bean
    public RedisSessionDAO redisSessionDAO()
    {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        // custom session key prefix
        //redisSessionDAO.setKeyPrefix("");
        // custom session value serializer, default is jdk serializer.
        redisSessionDAO.setValueSerializer(new FstSerializer());
        redisSessionDAO.setExpire(30 * 60);
        return redisSessionDAO;
    }

    /**
     * 会话管理器
     */
    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 加入缓存管理器
        sessionManager.setCacheManager(redisCacheManager());
        // 去掉JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionIdCookie(simpleCookie());
        // 自定义SessionDao
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     *  安全管理器  securityManager
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        // 注入缓存管理器
        securityManager.setCacheManager(redisCacheManager());
        //注入记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        // 注入session管理
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }


    /**
     * Shiro过滤器配置
     * */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // ......
        return shiroFilterFactoryBean;
    }

    private SimpleCookie simpleCookie()
    {
        SimpleCookie simpleCookie = new SimpleCookie("shiro.sesssion");
        simpleCookie.setPath("/");
        return simpleCookie;
    }

    /**
     * cookie 属性设置
     */
    private SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(-1);
        simpleCookie.setPath("/");
        return simpleCookie;
    }

    /** rememberMeManager管理器
     * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
     */
    private CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCipherKey(Base64.decode("xxxxxx"));
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }


    /**
     * 会话调度器
     */
    @Bean
    public ExecutorServiceSessionValidationScheduler scheduler(){
        ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
        scheduler.setInterval(30 * 60 * 1000);
        return scheduler;
    }

    /**
     * 同一个用户多设备登录限制
     */
    public KickoutSessionControlFilter kickoutSessionControlFilter(){
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        kickoutSessionControlFilter.setCacheManager(redisCacheManager());
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
        kickoutSessionControlFilter.setMaxSession(1);
        // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
        kickoutSessionControlFilter.setKickoutAfter(false);
        kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1");
        return kickoutSessionControlFilter;
    }

    /**
     * 在thymeleaf 使用shiro页面标签
     * */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

    /**
     * 开启Shiro注解通知器
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
     */
    @Bean
    @ConditionalOnMissingBean
    public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultApp = new DefaultAdvisorAutoProxyCreator();
        defaultApp.setProxyTargetClass(true);
        return defaultApp;
    }
}

prueba

Después de iniciar e iniciar sesión en el sistema, verifique el estado del caché de la consola redis

微信截图_20220715154434.png

微信截图_20220715154523.png

微信截图_20220715154530.png

Otros artículos

shiro integra JWT

reino personalizado de shiro

shiro obtener usuarios en línea

Supongo que te gusta

Origin juejin.im/post/7120506532793417741
Recomendado
Clasificación