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 /** 065 * Returns the {@code PatternMatcher} used when determining if an incoming request's path 066 * matches a configured filter chain. Unless overridden, the 067 * default implementation is an {@link AntPathMatcher AntPathMatcher}. 068 * 069 * @return the {@code PatternMatcher} used when determining if an incoming request's path 070 * matches a configured filter chain. 071 */ 072 public PatternMatcher getPathMatcher() { 073 return pathMatcher; 074 } 075 076 /** 077 * Sets the {@code PatternMatcher} used when determining if an incoming request's path 078 * matches a configured filter chain. Unless overridden, the 079 * default implementation is an {@link AntPathMatcher AntPathMatcher}. 080 * 081 * @param pathMatcher the {@code PatternMatcher} used when determining if an incoming request's path 082 * matches a configured filter chain. 083 */ 084 public void setPathMatcher(PatternMatcher pathMatcher) { 085 this.pathMatcher = pathMatcher; 086 } 087 088 public FilterChainManager getFilterChainManager() { 089 return filterChainManager; 090 } 091 092 @SuppressWarnings({"UnusedDeclaration"}) 093 public void setFilterChainManager(FilterChainManager filterChainManager) { 094 this.filterChainManager = filterChainManager; 095 } 096 097 public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { 098 FilterChainManager filterChainManager = getFilterChainManager(); 099 if (!filterChainManager.hasChains()) { 100 return null; 101 } 102 103 final String requestURI = getPathWithinApplication(request); 104 final String requestURINoTrailingSlash = removeTrailingSlash(requestURI); 105 106 //the 'chain names' in this implementation are actually path patterns defined by the user. We just use them 107 //as the chain name for the FilterChainManager's requirements 108 for (String pathPattern : filterChainManager.getChainNames()) { 109 // If the path does match, then pass on to the subclass implementation for specific checks: 110 if (pathMatches(pathPattern, requestURI)) { 111 if (LOGGER.isTraceEnabled()) { 112 LOGGER.trace("Matched path pattern [{}] for requestURI [{}]. " 113 + "Utilizing corresponding filter chain...", pathPattern, Encode.forHtml(requestURI)); 114 } 115 return filterChainManager.proxy(originalChain, pathPattern); 116 } else { 117 118 // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" both can access the resource 119 // but the pathPattern match "/resource/menus" can not match "resource/menus/" 120 // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect 121 122 pathPattern = removeTrailingSlash(pathPattern); 123 124 if (pathMatches(pathPattern, requestURINoTrailingSlash)) { 125 if (LOGGER.isTraceEnabled()) { 126 LOGGER.trace("Matched path pattern [{}] for requestURI [{}]. " 127 + "Utilizing corresponding filter chain...", 128 pathPattern, Encode.forHtml(requestURINoTrailingSlash)); 129 } 130 return filterChainManager.proxy(originalChain, pathPattern); 131 } 132 } 133 } 134 135 return null; 136 } 137 138 /** 139 * Returns {@code true} if an incoming request path (the {@code path} argument) 140 * matches a configured filter chain path (the {@code pattern} argument), {@code false} otherwise. 141 * <p/> 142 * Simply delegates to 143 * <b><code>{@link #getPathMatcher() getPathMatcher()}. 144 * {@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>. 145 * Subclass implementers should think carefully before overriding this method, as typically a custom 146 * {@code PathMatcher} should be configured for custom path matching behavior instead. Favor OO composition 147 * rather than inheritance to limit your exposure to Shiro implementation details which may change over time. 148 * 149 * @param pattern the pattern to match against 150 * @param path the value to match with the specified {@code pattern} 151 * @return {@code true} if the request {@code path} matches the specified filter chain url {@code pattern}, 152 * {@code false} otherwise. 153 */ 154 protected boolean pathMatches(String pattern, String path) { 155 PatternMatcher pathMatcher = getPathMatcher(); 156 return pathMatcher.matches(pattern, path); 157 } 158 159 /** 160 * Merely returns 161 * <code>WebUtils.{@link org.apache.shiro.web.util.WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) 162 * getPathWithinApplication(request)}</code> 163 * and can be overridden by subclasses for custom request-to-application-path resolution behavior. 164 * 165 * @param request the incoming {@code ServletRequest} 166 * @return the request's path within the application. 167 */ 168 protected String getPathWithinApplication(ServletRequest request) { 169 return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); 170 } 171 172 private static String removeTrailingSlash(String path) { 173 if (path != null && !DEFAULT_PATH_SEPARATOR.equals(path) 174 && path.endsWith(DEFAULT_PATH_SEPARATOR)) { 175 return path.substring(0, path.length() - 1); 176 } 177 return path; 178 } 179}