项目场景:
项目使用的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;
@Override
protected boolean isAccessAllowed(ServletRequest req, ServletResponse response, Object mappedValue) throws Exception {
HttpServletRequest request = WebUtils.toHttp(req);
// check the original and decoded values
return 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));
}
@Override
protected 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
{
@Override
public Class<MySpringShiroFilter> getObjectType()
{
return MySpringShiroFilter.class;
}
@Override
protected 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();