项目场景:
项目使用的springboot+shiro,静态资源是直接使用的springboot的ResourceHandlerRegistry来进行配置访问的,没有使用Nginx,Apache等
原因分析:
在做文件预览时发现,中文名称资源无法访问。多次断点调试,发现原来是最近shiro中引入了一个全局的InvalidRequestFilter ,其中blockNonAscii默认为true,路径含中文会被过滤掉。
具体源码如下:
/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements. See the NOTICE file* distributed with this work for additional information* regarding copyright ownership. The ASF licenses this file* to you 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 org.apache.shiro.web.filter;import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;/*** A request filter that blocks malicious requests. Invalid request will respond with a 400 response code.* <p>* This filter checks and blocks the request if the following characters are found in the request URI:* <ul>* <li>Semicolon - can be disabled by setting {@code blockSemicolon = false}</li>* <li>Backslash - can be disabled by setting {@code blockBackslash = false}</li>* <li>Non-ASCII characters - can be disabled by setting {@code blockNonAscii = false}, the ability to disable this check will be removed in future version.</li>* </ul>** @see <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/firewall/StrictHttpFirewall.html">This class was inspired by Spring Security StrictHttpFirewall</a>* @since 1.6*/
public class InvalidRequestFilter extends AccessControlFilter {private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));private boolean blockSemicolon = true;private boolean blockBackslash = !Boolean.getBoolean(WebUtils.ALLOW_BACKSLASH);private boolean blockNonAscii = true;@Overrideprotected boolean isAccessAllowed(ServletRequest req, ServletResponse response, Object mappedValue) throws Exception {HttpServletRequest request = WebUtils.toHttp(req);// check the original and decoded valuesreturn isValid(request.getRequestURI()) // user request string (not decoded)&& isValid(request.getServletPath()) // decoded servlet part&& isValid(request.getPathInfo()); // decoded path info (may be null)}private boolean isValid(String uri) {return !StringUtils.hasText(uri)|| ( !containsSemicolon(uri)&& !containsBackslash(uri)&& !containsNonAsciiCharacters(uri));}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {WebUtils.toHttp(response).sendError(400, "Invalid request");return false;}private boolean containsSemicolon(String uri) {if (isBlockSemicolon()) {return SEMICOLON.stream().anyMatch(uri::contains);}return false;}private boolean containsBackslash(String uri) {if (isBlockBackslash()) {return BACKSLASH.stream().anyMatch(uri::contains);}return false;}private boolean containsNonAsciiCharacters(String uri) {if (isBlockNonAscii()) {return !containsOnlyPrintableAsciiCharacters(uri);}return false;}private static boolean containsOnlyPrintableAsciiCharacters(String uri) {int length = uri.length();for (int i = 0; i < length; i++) {char c = uri.charAt(i);if (c < '\u0020' || c > '\u007e') {return false;}}return true;}public boolean isBlockSemicolon() {return blockSemicolon;}public void setBlockSemicolon(boolean blockSemicolon) {this.blockSemicolon = blockSemicolon;}public boolean isBlockBackslash() {return blockBackslash;}public void setBlockBackslash(boolean blockBackslash) {this.blockBackslash = blockBackslash;}public boolean isBlockNonAscii() {return blockNonAscii;}public void setBlockNonAscii(boolean blockNonAscii) {this.blockNonAscii = blockNonAscii;}
}
解决办法:
1.自定义过滤器,继承ShiroFilterFactoryBean,设置blockNonAscii为false。
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.InvalidRequestFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.beans.factory.BeanInitializationException;
import javax.servlet.Filter;
import java.util.Map;/*** 自定义ShiroFilterFactoryBean解决资源中文路径问题** @author ruoyi*/
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean
{@Overridepublic Class<MySpringShiroFilter> getObjectType(){return MySpringShiroFilter.class;}@Overrideprotected AbstractShiroFilter createInstance() throws Exception{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);Map<String, Filter> filterMap = manager.getFilters();Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());if (invalidRequestFilter instanceof InvalidRequestFilter){// 此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug((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 MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver);}private static final class MySpringShiroFilter extends AbstractShiroFilter{protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver){if (webSecurityManager == null){throw new IllegalArgumentException("WebSecurityManager property cannot be null.");}else{this.setSecurityManager(webSecurityManager);if (resolver != null){this.setFilterChainResolver(resolver);}}}}
}
2.替换ShiroConfig.java中的过滤器配置为自定义配置类
shiroFilterFactoryBean方法
// ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();