Java tutorial
/* * 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.jsecurity.web.config; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jsecurity.config.ConfigurationException; import org.jsecurity.config.IniConfiguration; import org.jsecurity.config.ReflectionBuilder; import org.jsecurity.mgt.RealmSecurityManager; import org.jsecurity.util.AntPathMatcher; import org.jsecurity.util.PatternMatcher; import static org.jsecurity.util.StringUtils.split; import org.jsecurity.web.DefaultWebSecurityManager; import org.jsecurity.web.WebUtils; import org.jsecurity.web.filter.PathConfigProcessor; import org.jsecurity.web.filter.authc.AnonymousFilter; import org.jsecurity.web.filter.authc.BasicHttpAuthenticationFilter; import org.jsecurity.web.filter.authc.FormAuthenticationFilter; import org.jsecurity.web.filter.authc.UserFilter; import org.jsecurity.web.filter.authz.PermissionsAuthorizationFilter; import org.jsecurity.web.filter.authz.RolesAuthorizationFilter; import org.jsecurity.web.servlet.AdviceFilter; import org.jsecurity.web.servlet.ProxiedFilterChain; import javax.servlet.*; import java.util.*; /** * A <code>WebConfiguration</code> that supports configuration via the * <a href="http://en.wikipedia.org/wiki/INI_file">.ini format</a>. * * @author Les Hazlewood * @since Jun 1, 2008 11:02:44 PM */ public class IniWebConfiguration extends IniConfiguration implements WebConfiguration { //TODO - complete JavaDoc private static final transient Log log = LogFactory.getLog(IniWebConfiguration.class); public static final String FILTERS = "filters"; public static final String URLS = "urls"; protected FilterConfig filterConfig; protected Map<String, List<Filter>> chains; protected PatternMatcher pathMatcher = new AntPathMatcher(); public IniWebConfiguration() { chains = new LinkedHashMap<String, List<Filter>>(); } /** * Returns the <code>PatternMatcher</code> used when determining if an incoming request's path * matches a configured filter chain path in the <code>[urls]</code> section. Unless overridden, the * default implementation is an {@link org.jsecurity.util.AntPathMatcher AntPathMatcher}. * * @return the <code>PatternMatcher</code> used when determining if an incoming request's path * matches a configured filter chain path in the <code>[urls]</code> section. * @since 0.9.0 final */ public PatternMatcher getPathMatcher() { return pathMatcher; } /** * Sets the <code>PatternMatcher</code> used when determining if an incoming request's path * matches a configured filter chain path in the <code>[urls]</code> section. Unless overridden, the * default implementation is an {@link org.jsecurity.util.AntPathMatcher AntPathMatcher}. * * @param pathMatcher the <code>PatternMatcher</code> used when determining if an incoming request's path * matches a configured filter chain path in the <code>[urls]</code> section. * @since 0.9.0 final */ public void setPathMatcher(PatternMatcher pathMatcher) { this.pathMatcher = pathMatcher; } /** * Returns the <code>FilterConfig</code> provided by the Servlet container at webapp startup. * * @return the <code>FilterConfig</code> provided by the Servlet container at webapp startup. */ public FilterConfig getFilterConfig() { return filterConfig; } /** * Sets the <code>FilterConfig</code> provided by the Servlet container at webapp startup. * * @param filterConfig the <code>FilterConfig</code> provided by the Servlet container at webapp startup. */ public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; } //TODO - JAVADOC public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { if (this.chains == null || this.chains.isEmpty()) { return null; } String requestURI = getPathWithinApplication(request); for (String path : this.chains.keySet()) { // If the path does match, then pass on to the subclass implementation for specific checks: if (pathMatches(path, requestURI)) { if (log.isTraceEnabled()) { log.trace("Matched path [" + path + "] for requestURI [" + requestURI + "]. " + "Utilizing corresponding filter chain..."); } return getChain(path, originalChain); } } return null; } /** * Returns the <code>FilterChain</code> to use for the specified application path, or <code>null</code> if the * original <code>FilterChain</code> should be used. * <p/> * The default implementation simply calls <code>this.chains.get(chainUrl)</code> to acquire the configured * <code>List<Filter></code> filter chain. If that configured chain is non-null and not empty, it is * returned, otherwise <code>null</code> is returned to indicate that the <code>originalChain</code> should be * used instead. * * @param chainUrl the configured filter chain url * @param originalChain the original FilterChain given by the Servlet container. * @return the <code>FilterChain</code> to use for the specified application path, or <code>null</code> if the * original <code>FilterChain</code> should be used. */ protected FilterChain getChain(String chainUrl, FilterChain originalChain) { List<Filter> pathFilters = this.chains.get(chainUrl); if (pathFilters != null && !pathFilters.isEmpty()) { return createChain(pathFilters, originalChain); } return null; } /** * Creates a new FilterChain based on the specified configured url filter chain and original chain. * <p/> * The input arguments are expected be be non-null and non-empty, since these conditions are accounted for in the * {@link #getChain(String, javax.servlet.FilterChain) getChain(chainUrl,originalChain)} implementation that * calls this method. * <p/> * The default implementation merely returns * <code>new {@link org.jsecurity.web.servlet.ProxiedFilterChain FilterChainWrapper(filters, originalChain)}</code>, * and can be overridden by subclasses for custom creation. * * @param filters the configured filter chain for the incoming request application path * @param originalChain the original FilterChain given by the Servlet container. * @return a new FilterChain based on the specified configured url filter chain and original chain. */ protected FilterChain createChain(List<Filter> filters, FilterChain originalChain) { return new ProxiedFilterChain(originalChain, filters); } /** * Returns <code>true</code> if an incoming request's path (the <code>path</code> argument) * matches a configured filter chain path in the <code>[urls]</code> section (the <code>pattern</code> argument), * <code>false</code> otherwise. * <p/> * Simply delegates to * <b><code>{@link #getPathMatcher() getPathMatcher()}.{@link org.jsecurity.util.PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>, * but can be overridden by subclasses for custom matching behavior. * * @param pattern the pattern to match against * @param path the value to match with the specified <code>pattern</code> * @return <code>true</code> if the request <code>path</code> matches the specified filter chain url <code>pattern</code>, * <code>false</code> otherwise. */ protected boolean pathMatches(String pattern, String path) { PatternMatcher pathMatcher = getPathMatcher(); return pathMatcher.matches(pattern, path); } /** * Merely returns * <code>WebUtils.{@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) getPathWithinApplication(request)}</code> * and can be overridden by subclasses for custom request-to-application-path resolution behavior. * * @param request the incoming <code>ServletRequest</code> * @return the request's path within the appliation. */ protected String getPathWithinApplication(ServletRequest request) { return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); } /** * Creates a new, uninitialized <code>SecurityManager</code> instance that will be used to build up * the JSecurity environment for the web application. * <p/> * The default implementation simply returns * <code>new {@link org.jsecurity.web.DefaultWebSecurityManager DefaultWebSecurityManager()};</code> * * @return a new, uninitialized <code>SecurityManager</code> instance that will be used to build up * the JSecurity environment for the web application. */ protected RealmSecurityManager newSecurityManagerInstance() { return new DefaultWebSecurityManager(); } /** * This implementation: * <ol> * <li>First builds the filter instances by processing the [filters] section</li> * <li>Builds a collection filter chains according to the definitions in the [urls] section</li> * <li>Initializes the filter instances in the order in which they were defined</li> * </ol> * * @param sections the configured .ini sections where the key is the section name (without [] brackets) * and the value is the key/value pairs inside that section. */ protected void afterSecurityManagerSet(Map<String, Map<String, String>> sections) { //filters section: Map<String, String> section = sections.get(FILTERS); Map<String, Filter> filters = getFilters(section); //urls section: section = sections.get(URLS); this.chains = createChains(section, filters); initFilters(this.chains); } protected void initFilters(Map<String, List<Filter>> chains) { if (chains == null || chains.isEmpty()) { return; } //add 'em to a set so we only initialize each one once: Set<Filter> filters = new LinkedHashSet<Filter>(); for (List<Filter> pathFilters : chains.values()) { filters.addAll(pathFilters); } //now initialize each one: for (Filter filter : filters) { initFilter(filter); } } /** * Initializes the filter by calling <code>filter.init( {@link #getFilterConfig() getFilterConfig()} );</code>. * * @param filter the filter to initialize with the <code>FilterConfig</code>. */ protected void initFilter(Filter filter) { try { filter.init(getFilterConfig()); } catch (ServletException e) { throw new ConfigurationException(e); } } @SuppressWarnings({ "unchecked" }) protected Map<String, Filter> getFilters(Map<String, String> section) { Map<String, Filter> filters = createDefaultFilters(); if (section != null && !section.isEmpty()) { ReflectionBuilder builder = new ReflectionBuilder(filters); Map built = builder.buildObjects(section); assertFilters(built); filters = (Map<String, Filter>) built; } return filters; } protected void assertFilters(Map<String, ?> map) { if (map == null || map.isEmpty()) { return; } for (Map.Entry<String, ?> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); assertFilter(key, value); } } protected void assertFilter(String name, Object o) throws ConfigurationException { if (!(o instanceof Filter)) { String msg = "[" + FILTERS + "] section specified a filter named '" + name + "', which does not " + "implement the " + Filter.class.getName() + " interface. Only Filter implementations may be " + "defined."; throw new ConfigurationException(msg); } } protected Map<String, Filter> createDefaultFilters() { Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); String name = "anon"; AdviceFilter filter = new AnonymousFilter(); filter.setName(name); filters.put(name, filter); name = "user"; filter = new UserFilter(); filter.setName(name); filters.put(name, filter); name = "authc"; filter = new FormAuthenticationFilter(); filter.setName(name); filters.put(name, filter); name = "authcBasic"; filter = new BasicHttpAuthenticationFilter(); filter.setName(name); filters.put(name, filter); name = "roles"; filter = new RolesAuthorizationFilter(); filter.setName(name); filters.put(name, filter); name = "perms"; filter = new PermissionsAuthorizationFilter(); filter.setName(name); filters.put(name, filter); return filters; } public Map<String, List<Filter>> createChains(Map<String, String> urls, Map<String, Filter> filters) { if (urls == null || urls.isEmpty()) { if (log.isDebugEnabled()) { log.debug("No urls to process."); } return null; } if (filters == null || filters.isEmpty()) { if (log.isDebugEnabled()) { log.debug("No filters to process."); } return null; } if (log.isTraceEnabled()) { log.trace("Before url processing."); } Map<String, List<Filter>> pathChains = new LinkedHashMap<String, List<Filter>>(urls.size()); for (Map.Entry<String, String> entry : urls.entrySet()) { String path = entry.getKey(); String value = entry.getValue(); if (log.isDebugEnabled()) { log.debug("Processing path [" + path + "] with value [" + value + "]"); } List<Filter> pathFilters = new ArrayList<Filter>(); //parse the value by tokenizing it to get the resulting filter-specific config entries // //e.g. for a value of // // "authc, roles[admin,user], perms[file:edit]" // // the resulting token array would equal // // { "authc", "roles[admin,user]", "perms[file:edit]" } // String[] filterTokens = split(value, ',', '[', ']', true, true); //each token is specific to each filter. //strip the name and extract any filter-specific config between brackets [ ] for (String token : filterTokens) { String[] nameAndConfig = token.split("\\[", 2); String name = nameAndConfig[0]; String config = null; if (nameAndConfig.length == 2) { config = nameAndConfig[1]; //if there was an open bracket, there was a close bracket, so strip it too: config = config.substring(0, config.length() - 1); } //now we have the filter name, path and (possibly null) path-specific config. Let's apply them: Filter filter = filters.get(name); if (filter == null) { String msg = "Path [" + path + "] specified a filter named '" + name + "', but that " + "filter has not been specified in the [" + FILTERS + "] section."; throw new ConfigurationException(msg); } if (filter instanceof PathConfigProcessor) { if (log.isDebugEnabled()) { log.debug("Applying path [" + path + "] to filter [" + name + "] " + "with config [" + config + "]"); } ((PathConfigProcessor) filter).processPathConfig(path, config); } pathFilters.add(filter); } if (!pathFilters.isEmpty()) { pathChains.put(path, pathFilters); } } if (pathChains.isEmpty()) { return null; } return pathChains; } }