Spring Boot + Shiro 使用 DefaultWebSessionManager 导致 Druid Monitor 监听不到 Session 问题解决方案

一、问题配置

项目中使用了 shiro-spring 快速集成 Shiro 到当前 Spring 环境中,配置如下:

  1. pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>  
   // ...
	<properties>
		<!-- shiro -->
		<shiro-spring.version>1.4.0</shiro-spring.version>
		<shiro-ehcache.version>1.5.1</shiro-ehcache.version>
	</properties>

	<dependencies>
		// ...
		<!-- apache-shiro -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>${shiro-spring.version}</version>
		</dependency>

		<!-- apache-shiro-ehcache 支持 -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>${shiro-ehcache.version}</version>
		</dependency>
	</dependencies>
    // ...
</project>

2. ShiroConfig.java

ShiroConfig 中的 ShiroFilter 配置方式在此处是必须的,网上部分教程使用的是如下的配置方式:

/**
 * 权限校验配置
 */
@Configuration
public class ShiroConfig {

    /**
     * 配置 Shiro 拦截器工厂
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 由于系统中所有的权限拦截已经通过 SpringMVC interceptor 拦截实现了, 这里将所有的请求交给 anno 处理,
        // 具体拦截由 SpringMVC interceptor 处理, 后续看情况迁移至 shiro 实现
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    
    // ...

   /**
     * 采用 DefaultWebSessionManager 替代掉容器内置的 Session 实现。
     */
    @Bean("sessionManager")
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //配置监听
        sessionManager.setSessionIdCookie(sessionIdCookie());
        // 设置 sessionDAO
        sessionManager.setSessionDAO(sessionDAO());
        // 配置 session
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        // 10 分钟检查一遍失效 session
        sessionManager.setSessionValidationInterval(10 * 60 * 1000);

        return sessionManager;
    }

}

这种配置本身并没有问题,但是在集成 Druid Monitor 监听 Session 的场景会存在问题。通常我们在 Spring-Boot 项目中继承 Druid 引入如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

然后 happy 的在 application.yml 中添加如下配置:

spring.datasource.druid:
  web-stat-filter:
    enabled: true
    url-pattern: /*
    exclusions: "/druid/*,/static/*,/webjars/*,/web-admin/*"
    # 开启 session 监控
    session-stat-enable: true
    session-stat-max-count: 1000
    # 监听 session 中的 login-user 属性
    principal-session-name: login-user
    profile-enable: true
  stat-view-servlet:
    enabled: true
    url-pattern: /druid/*
    reset-enable: true
    login-username: admin
    login-password: 123456
    allow:
    deny:
  filter:
    slf4j:
      enabled: true
      statement-create-after-log-enabled: false
      statement-log-enabled: false
      statement-executable-sql-log-enable: true
      statement-log-error-enabled: true
      result-set-log-enabled: false

但是当我们打开 druid monitor 会发现 session 监控并没有生效。

二、原因分析

 druid-spring-boot-starter 自动配置 com.alibaba.druid.support.http.WebStatFilter 的时候使用的是 FilterRegistrationBean,源代码如下:

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.druid.spring.boot.autoconfigure.stat;

import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

/**
 * @author lihengming [89921218@qq.com]
 */
@ConditionalOnWebApplication
@ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true", matchIfMissing = true)
public class DruidWebStatFilterConfiguration {
    @Bean
    public FilterRegistrationBean webStatFilterRegistrationBean(DruidStatProperties properties) {
        DruidStatProperties.WebStatFilter config = properties.getWebStatFilter();
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        WebStatFilter filter = new WebStatFilter();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(config.getUrlPattern() != null ? config.getUrlPattern() : "/*");
        registrationBean.addInitParameter("exclusions", config.getExclusions() != null ? config.getExclusions() : "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        if (config.getSessionStatEnable() != null) {
            registrationBean.addInitParameter("sessionStatEnable", config.getSessionStatEnable());
        }
        if (config.getSessionStatMaxCount() != null) {
            registrationBean.addInitParameter("sessionStatMaxCount", config.getSessionStatMaxCount());
        }
        if (config.getPrincipalSessionName() != null) {
            registrationBean.addInitParameter("principalSessionName", config.getPrincipalSessionName());
        }
        if (config.getPrincipalCookieName() != null) {
            registrationBean.addInitParameter("principalCookieName", config.getPrincipalCookieName());
        }
        if (config.getProfileEnable() != null) {
            registrationBean.addInitParameter("profileEnable", config.getProfileEnable());
        }
        return registrationBean;
    }
}

DruidWebStatFilterConfiguration 在配置 WebStatFilter 的时候并没有指定顺序(其实 Servlet 标准中也没有通过指定 order 指定 Filter 拦截器顺序的机制,Filter 顺序按照其配置的先后,先配置的在前,后配置的在后),所以其默认的顺序为 FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER(越小优先级越高),而通过 ShiroFilterFactoryBean 配置的 Filter 是不会参加这个排序过程的,永远都是最后一个,因为 ShiroFilterFactoryBean 向容器注册 Filter 的时机在 FilterRegistrationBean 之后。

 Druid 的 WebStatFilter 在获取 Session 信息的时候,此时的 Session 是原容器的 Session 信息,故不能获取 Shiro 管理的 Session 信息(Shiro  管理的 Session 信息需要再经过 org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter)之后才会生效,所以就会出现上述的问题。

三、解决方案

从 Shiro Filter 配置入手,直接通过 ShiroFactoryBean 传进 Filter, 然后创建 FilterRegistrationBean,配置 order 小于 FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER 即可。具体配置代码如下:

/**
 * 权限校验配置
 */
@Configuration
public class ShiroConfig {

    /**
     * 配置 Shiro 拦截器配置
     *
     * @param securityManager
     * @return
     */
    @SneakyThrows
    @Bean
    public FilterRegistrationBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 由于系统中所有的权限拦截已经通过 SpringMVC interceptor 拦截实现了, 这里将所有的请求交给 anno 处理,
        // 具体拦截由 SpringMVC interceptor 处理, 后续看情况迁移至 shiro 实现
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        Object filter = shiroFilterFactoryBean.getObject();
        FilterRegistrationBean<Filter> register = new FilterRegistrationBean<>();
        register.setFilter((Filter) filter);
        register.setOrder(FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER - 10);
        register.addUrlPatterns("/*");
        return register;
    }

    // ...
    
    /**
     * 采用 DefaultWebSessionManager 替代掉容器内置的 Session 实现。
     */
    @Bean("sessionManager")
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //配置监听
        sessionManager.setSessionIdCookie(sessionIdCookie());
        // 设置 sessionDAO
        sessionManager.setSessionDAO(sessionDAO());
        // 配置 session
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        // 10 分钟检查一遍失效 session
        sessionManager.setSessionValidationInterval(10 * 60 * 1000);

        return sessionManager;
    }
}

四、验证

项目启动,打开 Druid Monitor,打开 Session 监控界面(注意:系统中需要有登录用户),结果如下:

具体代码可以参看笔者个开源项目 QW-ADMIN 中 com.qiwen.base.config.shiro.ShiroConfig 相关配置。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值