由于shiro1.6出现了安全漏洞,需要进行1.7的升级
问题描述
进行升级后有个url突然访问不了,HTTP返回了400,Invaild Request。
接收的URL为 xxx/detail/{name}。
前端传递的地址为 xxx%2fdetail%2f%e6%88%bf%e4%ba%a7(带有中文,已用URL编码)
思路
-
- 看到HTTP响应400,我在想是前端参数出了什么问题吗?还是后台接收参数改动了吗?
- 看了git记录,这边没人改动,那这就很灵性了,只能通过调试找出哪个地方访问了400了,我是全局搜了一下代码里哪里有400或者Invaild Request
- 然后通过git记录发现只是升级了一些框架的源码,通过排除法,先找最有可能出现错误的框架,这里是spring和shiro都回退,发现果然是升级shiro出了问题,(把shiro回退到1.6问题就没出现)
然后就开始打断点调试
-
先给出结论:
- url带有中文字符shiro校验不通过
- 排查流程
-
org.apache.shiro.web.filter.AccessControlFilter 校验失败了
-
子类org.apache.shiro.web.filter.InvalidRequestFilter 返回了400
-
这时候就要找出1.7的版本,为什么会被拦截掉了。
然后调试一下1.6的版本为什么没出问题,比较类的差异,通过github直接看,或者直接修改版本IDEA支持自动反编译。
-
然后通过调试发现,问题出现在这一句isValid(request.getPathInfo())
-
1.6版本是只校验了uri,uri经过了URL编码是可以通过这个方法的
-
但是1.7 request.getPathInfo()是返回的URL解码过的路径,包含了中文,从方法名可以看出
-
containsNonAsciiCharacters(uri)这个方法没办法通过校验
- 解决问题
- 那知道了问题,可以从多个方向来解决这个问题,本人选择了一个方向是把一个校验开关属性设置为false。
- 其实最好的方法还是遵循规范,URL就不应该带中文字符,本人是属于后期维护这个项目,业务也不清晰,如果修改了url业务代码影响较大,但是也不能把校验全都关了,你查看shiro升级的日志发现就是加了一个这个全局过滤器和一些代码来修复漏洞。 选择了一个比较折衷的方法,就是把中文校验给关了。(这里补充一下 修改request.getPathInfo()怕对全局影响也比较大)
-
即注入在spring的 InvalidRequestFilter的blockNonAscii属性设置为false
-
这时候你也要调试源码。
简单说一下我的思路:一看到filter肯定是职责链设计模式,先考虑什么地方注入了这个filter.查看该类的引用
-
然后查看谁用了这个枚举
org.apache.shiro.spring.web.ShiroFilterFactoryBean
-
createFilterChainManager() 在这里用到
/**
* This implementation:
* <ol>
* <li>Ensures the required {@link #setSecurityManager(org.apache.shiro.mgt.SecurityManager) securityManager}
* property has been set</li>
* <li>{@link #createFilterChainManager() Creates} a {@link FilterChainManager} instance that reflects the
* configured {@link #setFilters(java.util.Map) filters} and
* {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}</li>
* <li>Wraps the FilterChainManager with a suitable
* {@link org.apache.shiro.web.filter.mgt.FilterChainResolver FilterChainResolver} since the Shiro Filter
* implementations do not know of {@code FilterChainManager}s</li>
* <li>Sets both the {@code SecurityManager} and {@code FilterChainResolver} instances on a new Shiro Filter
* instance and returns that filter instance.</li>
* </ol>
*
* @return a new Shiro Filter reflecting any configured filters and filter chain definitions.
* @throws Exception if there is a problem creating the AbstractShiroFilter instance.
*/
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
//Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
-
发现在这里filter都是通过FilterChainManager进行管理的,并会进行初始化的创建
-
其实,这时候你还要确认一件事,就是项目的filter是否是全局唯一的,会不会每次都进行new,我是通过调试源码进行判断的)
-
我需要在filter初始化后修改这个InvalidRequestFilter的默认属性。
-
所以我这边的做法是自定义了一个ShiroFilterFactoryBean
/**
* 自定义的 CustomShiroFilterFactoryBean, 使用了自定义的serveletresponse, 避免重定向到登录页面后, url里出现JSESSIONID=xxx
*
* @see ShiroFilterFactoryBean ShiroFilterFactoryBean
*
* @author <a href="mailto:ningyaobai@gzkit.com.cn">bernix</a>
* 十一月 18, 2016
* @version 1.0
*/
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {
private static final Logger logger = LoggerFactory.getLogger(CustomShiroFilterFactoryBean.class);
@Override
public Class getObjectType() {
return CustomSpringShiroFilter.class;
}
@Override
protected AbstractShiroFilter createInstance() throws Exception {
logger.debug("Creating Shiro Filter instance.");
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
//Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
// URL携带中文400,servletPath中文校验bug
Map<String, Filter> filterMap = manager.getFilters();
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
if (invalidRequestFilter instanceof InvalidRequestFilter) {
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
}
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
return new CustomSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
}
码哥zhkkjun 给出了另一个修改的方案 具体可以自己测试一下是否有问题
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {
@Override
protected FilterChainManager createFilterChainManager() {
FilterChainManager manager = super.createFilterChainManager();
// URL携带中文400,servletPath中文校验bug
Map<String, Filter> filterMap = manager.getFilters();
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
if (invalidRequestFilter instanceof InvalidRequestFilter) {
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
}
return manager;
}
}
- 问题完美解决