转载,原创链接:https://blog.csdn.net/Dreamcatcher_yxc/article/details/106156988
一、问题配置
项目中使用了 shiro-spring 快速集成 Shiro 到当前 Spring 环境中,配置如下:
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>
- 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;
}
}
这种配置本身并没有问题,但是在集成 Spring 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 相关配置。
————————————————
版权声明:本文为CSDN博主「Dreamcatcher_yxc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Dreamcatcher_yxc/article/details/106156988