001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.web.filter.mgt; 020 021import org.apache.shiro.util.AntPathMatcher; 022import org.apache.shiro.util.PatternMatcher; 023import org.apache.shiro.web.util.WebUtils; 024import org.owasp.encoder.Encode; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import javax.servlet.FilterChain; 029import javax.servlet.FilterConfig; 030import javax.servlet.ServletRequest; 031import javax.servlet.ServletResponse; 032 033/** 034 * A {@code FilterChainResolver} that resolves {@link FilterChain}s based on url path 035 * matching, as determined by a configurable {@link #setPathMatcher(PatternMatcher) PathMatcher}. 036 * <p/> 037 * This implementation functions by consulting a {@link org.apache.shiro.web.filter.mgt.FilterChainManager} 038 * for all configured filter chains (keyed 039 * by configured path pattern). If an incoming Request path matches one of the configured path patterns (via 040 * the {@code PathMatcher}, the corresponding configured {@code FilterChain} is returned. 041 * 042 * @since 1.0 043 */ 044public class PathMatchingFilterChainResolver implements FilterChainResolver { 045 046 private static final Logger LOGGER = LoggerFactory.getLogger(PathMatchingFilterChainResolver.class); 047 048 private static final String DEFAULT_PATH_SEPARATOR = "/"; 049 050 private FilterChainManager filterChainManager; 051 052 private PatternMatcher pathMatcher; 053 054 public PathMatchingFilterChainResolver() { 055 this.pathMatcher = new AntPathMatcher(); 056 this.filterChainManager = new DefaultFilterChainManager(); 057 } 058 059 public PathMatchingFilterChainResolver(FilterConfig filterConfig) { 060 this.pathMatcher = new AntPathMatcher(); 061 this.filterChainManager = new DefaultFilterChainManager(filterConfig); 062 } 063 064 public PathMatchingFilterChainResolver caseInsensitive(boolean caseInsensitive) { 065 setCaseInsensitive(caseInsensitive); 066 return this; 067 } 068 069 /** 070 * Returns the {@code PatternMatcher} used when determining if an incoming request's path 071 * matches a configured filter chain. Unless overridden, the 072 * default implementation is an {@link AntPathMatcher AntPathMatcher}. 073 * 074 * @return the {@code PatternMatcher} used when determining if an incoming request's path 075 * matches a configured filter chain. 076 */ 077 public PatternMatcher getPathMatcher() { 078 return pathMatcher; 079 } 080 081 /** 082 * Sets the {@code PatternMatcher} used when determining if an incoming request's path 083 * matches a configured filter chain. Unless overridden, the 084 * default implementation is an {@link AntPathMatcher AntPathMatcher}. 085 * 086 * @param pathMatcher the {@code PatternMatcher} used when determining if an incoming request's path 087 * matches a configured filter chain. 088 */ 089 public void setPathMatcher(PatternMatcher pathMatcher) { 090 this.pathMatcher = pathMatcher; 091 } 092 093 public boolean isCaseInsensitive() { 094 return pathMatcher != null && pathMatcher.isCaseInsensitive(); 095 } 096 097 public void setCaseInsensitive(boolean caseInsensitive) { 098 if (pathMatcher != null) { 099 pathMatcher.setCaseInsensitive(caseInsensitive); 100 } 101 if (filterChainManager != null) { 102 filterChainManager.setCaseInsensitive(caseInsensitive); 103 } 104 } 105 106 public FilterChainManager getFilterChainManager() { 107 return filterChainManager; 108 } 109 110 @SuppressWarnings({"UnusedDeclaration"}) 111 public void setFilterChainManager(FilterChainManager filterChainManager) { 112 this.filterChainManager = filterChainManager; 113 } 114 115 public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { 116 FilterChainManager filterChainManager = getFilterChainManager(); 117 if (!filterChainManager.hasChains()) { 118 return null; 119 } 120 121 final String requestURI = getPathWithinApplication(request); 122 final String requestURINoTrailingSlash = removeTrailingSlash(requestURI); 123 124 //the 'chain names' in this implementation are actually path patterns defined by the user. We just use them 125 //as the chain name for the FilterChainManager's requirements 126 for (String pathPattern : filterChainManager.getChainNames()) { 127 // If the path does match, then pass on to the subclass implementation for specific checks: 128 if (pathMatches(pathPattern, requestURI)) { 129 if (LOGGER.isTraceEnabled()) { 130 LOGGER.trace("Matched path pattern [{}] for requestURI [{}]. " 131 + "Utilizing corresponding filter chain...", pathPattern, Encode.forHtml(requestURI)); 132 } 133 return filterChainManager.proxy(originalChain, pathPattern); 134 } else { 135 136 // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" both can access the resource 137 // but the pathPattern match "/resource/menus" can not match "resource/menus/" 138 // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect 139 140 pathPattern = removeTrailingSlash(pathPattern); 141 142 if (pathMatches(pathPattern, requestURINoTrailingSlash)) { 143 if (LOGGER.isTraceEnabled()) { 144 LOGGER.trace("Matched path pattern [{}] for requestURI [{}]. " 145 + "Utilizing corresponding filter chain...", 146 pathPattern, Encode.forHtml(requestURINoTrailingSlash)); 147 } 148 return filterChainManager.proxy(originalChain, pathPattern); 149 } 150 } 151 } 152 153 return null; 154 } 155 156 /** 157 * Returns {@code true} if an incoming request path (the {@code path} argument) 158 * matches a configured filter chain path (the {@code pattern} argument), {@code false} otherwise. 159 * <p/> 160 * Simply delegates to 161 * <b><code>{@link #getPathMatcher() getPathMatcher()}. 162 * {@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>. 163 * Subclass implementers should think carefully before overriding this method, as typically a custom 164 * {@code PathMatcher} should be configured for custom path matching behavior instead. Favor OO composition 165 * rather than inheritance to limit your exposure to Shiro implementation details which may change over time. 166 * 167 * @param pattern the pattern to match against 168 * @param path the value to match with the specified {@code pattern} 169 * @return {@code true} if the request {@code path} matches the specified filter chain url {@code pattern}, 170 * {@code false} otherwise. 171 */ 172 protected boolean pathMatches(String pattern, String path) { 173 PatternMatcher pathMatcher = getPathMatcher(); 174 return pathMatcher.matches(pattern, path); 175 } 176 177 /** 178 * Merely returns 179 * <code>WebUtils.{@link org.apache.shiro.web.util.WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) 180 * getPathWithinApplication(request)}</code> 181 * and can be overridden by subclasses for custom request-to-application-path resolution behavior. 182 * 183 * @param request the incoming {@code ServletRequest} 184 * @return the request's path within the application. 185 */ 186 protected String getPathWithinApplication(ServletRequest request) { 187 return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); 188 } 189 190 private static String removeTrailingSlash(String path) { 191 if (path != null && !DEFAULT_PATH_SEPARATOR.equals(path) 192 && path.endsWith(DEFAULT_PATH_SEPARATOR)) { 193 return path.substring(0, path.length() - 1); 194 } 195 return path; 196 } 197}