[5]深入浅出工作开源框架Camunda: 解读 camunda-webapp 笔记之SecurityFilterRules

[5]深入浅出工作开源框架Camunda: 解读 camunda-webapp 笔记之SecurityFilterRules,第1张

本文的背景是基于camunda 7.16的版本;Camunda 7.16的运行版本在《[1]深入浅出工作开源框架Camunda: 安装和使用》 一文中已经提到如何下载;其默认访问的URL为http://127.0.0.1:8080/camunda/app/welcome/default/#!/welcome

那么上面的Camunda Web程序是如何工作的?其代码原理是什么呢?本章节笔者将会把我最近研究的结果和大家一起以笔记的形式分享。
(1)Web的应用程序在运行的程序中被拆成了两个jar

存放的是基于一个基于Spring的Web应用程序(注意其还不是SpringBoot的应用程序)

另外一个是封装了JS和CSS的Webjar,其代码结构如下:

需要注意的是,其里面有一个META-INF.resources.webjars.camunda.securityFilterRules.json

{
  "pathFilter": {
    "deniedPaths" : [
      { "path": "/api/engine/.*", "methods" : "*" },
      { "path": "/api/cockpit/.*", "methods" : "*" },
      { "path": "/api/tasklist/.*", "methods" : "*" },
      { "path": "/api/admin/.*", "methods" : "*" },
      { "path": "/app/tasklist/{engine}/.*", "methods" : "*" },
      { "path": "/app/cockpit/{engine}/.*", "methods" : "*" },
      { "path": "/app/welcome/{engine}/.*", "methods" : "*" },
      { "path": "/app/admin/{engine}/.*", "methods" : "*" }
    ],
    "allowedPaths" : [
      { "path": "/api/engine/engine/", "methods" : "GET" },
      { "path": "/api/{app:cockpit}/plugin/{plugin}/static/.*", "methods" : "GET" },
      { "path": "/api/{app:cockpit}/plugin/{plugin}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer" },
      { "path": "/api/engine/engine/{engine}/identity/password-policy", "methods" : "GET" },
      { "path": "/api/engine/engine/{engine}/identity/password-policy", "methods" : "POST" },
      { "path": "/api/admin/auth/user/{engine}", "methods" : "GET" },
      { "path": "/api/admin/auth/user/{engine}/logout", "methods" : "POST" },
      { "path": "/api/admin/auth/user/{engine}/login/{app}", "methods" : "POST" },
      { "path": "/api/admin/auth/user/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer" },
      { "path": "/api/admin/setup/{engine}/user/create", "methods" : "POST" },
      { "path": "/api/admin/setup/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer" },
      { "path": "/api/{app:admin}/plugin/license/{engine}/check-key", "methods" : "GET" },
      { "path": "/api/{app:admin}/plugin/{plugin}/static/.*", "methods" : "GET" },
      { "path": "/api/{app:admin}/plugin/{plugin}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer" },
      { "path": "/api/{app:tasklist}/plugin/{plugin}/static/.*", "methods" : "GET" },
      { "path": "/api/{app:tasklist}/plugin/{plugin}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer" },
      { "path": "/api/engine/engine/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer" },
      { "path": "/app/{app:cockpit}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer" },
      { "path": "/app/{app:tasklist}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer" },
      { "path": "/app/{app:welcome}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer" },
      { "path": "/app/{app:admin}/{engine}/.*", "methods" : "*", "authorizer" : "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer" }
    ]
  }
}

其定义了基本的Filter的原则。其包括了两方面的授权Filter:

  • org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer
    针对Camunda的API的请求的授权,其代码如下:
package org.camunda.bpm.webapp.impl.security.filter;
import java.util.Map;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;
import org.camunda.bpm.webapp.impl.security.auth.Authentications;
/**
 * 

This is a {@link RequestAuthorizer} which authorizes all process engine api * requests based on the current authentication

* * @author Daniel Meyer * @author nico.rehwaldt */
public class EngineRequestAuthorizer implements RequestAuthorizer { @Override public Authorization authorize(Map<String, String> parameters) { Authentications authentications = Authentications.getCurrent(); if (authentications == null) { // no authentications --> reject request to app return Authorization.denied(Authentication.ANONYMOUS); } else { String engineName = parameters.get("engine"); Authentication engineAuth = authentications.getAuthenticationForProcessEngine(engineName); return Authorization.grantedUnlessNull(engineAuth); } } }

从上面来看,当前的所有的认证信息,都是存放在调用org.camunda.bpm.webapp.impl.security.auth.Authentications 类的ThreadLocal当中,其会维护一个
authentications 的Map,其键值是engineName的名字,其值是一个org.camunda.bpm.webapp.impl.security.auth.Authentication的对象。

protected Map<String, Authentication> authentications = new HashMap<String, Authentication>();

org.camunda.bpm.webapp.impl.security.auth.Authentication的结构如下:

package org.camunda.bpm.webapp.impl.security.auth;

import java.io.Serializable;
import java.security.Principal;

/**
 * 

Represents an active authentication of a given identity (usually a user).

* *

In camunda webapps, an authentication exists between some identity (user) and * a process engine

* *

Implements java.security.Principal so that this object may be used everywhere where a * {@link Principal} is required.

* * @author Daniel Meyer * */
public class Authentication implements Principal, Serializable { public static final Authentication ANONYMOUS = new Authentication(null, null); private static final long serialVersionUID = 1L; protected final String identityId; protected final String processEngineName; public Authentication(String identityId, String processEngineName) { this.identityId = identityId; this.processEngineName = processEngineName; } /** * java.security.Principal implementation: return the id of the identity * (userId) behind this authentication */ public String getName() { return identityId; } /** * @return the id of the identity * (userId) behind this authentication */ public String getIdentityId() { return identityId; } /** * @return return the name of the process engine for which this authentication * was established. */ public String getProcessEngineName() { return processEngineName; } }

当前只有实现Authentication的子类只有一个org.camunda.bpm.webapp.impl.security.auth.UserAuthentication, 其里面会包括下面的用户相关的信息:

  protected List<String> groupIds;
  protected List<String> tenantIds;
  protected Set<String> authorizedApps;

另外从上面的代码可以看出,其只管engine的rest-api 相关的授权即可;

  • org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer
    针对Camunda的Application的请求的授权,比如CSS,JS,HTML等
package org.camunda.bpm.webapp.impl.security.filter;

import java.util.Map;
import org.camunda.bpm.cockpit.Cockpit;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;
import org.camunda.bpm.webapp.impl.security.auth.Authentications;
import org.camunda.bpm.webapp.impl.security.auth.UserAuthentication;

/**
 * 

This matcher can be used for restricting access to an app.

* * @author Daniel Meyer * @author nico.rehwaldt */
public class ApplicationRequestAuthorizer implements RequestAuthorizer { @Override public Authorization authorize(Map<String, String> parameters) { Authentications authentications = Authentications.getCurrent(); if (authentications == null) { // the user is not authenticated // grant user anonymous access return grantAnnonymous(); } else { String engineName = parameters.get("engine"); String appName = parameters.get("app"); Authentication engineAuth = authentications.getAuthenticationForProcessEngine(engineName); if (engineAuth == null) { // the user is not authenticated // grant user anonymous access return grantAnnonymous(); } // get process engine ProcessEngine processEngine = Cockpit.getProcessEngine(engineName); if (processEngine == null) { // the process engine does not exist // grant user anonymous access return grantAnnonymous(); } // check authorization if (engineAuth instanceof UserAuthentication) { UserAuthentication userAuth = (UserAuthentication) engineAuth; if (userAuth.isAuthorizedForApp(appName)) { return Authorization.granted(userAuth).forApplication(appName); } else { return Authorization.denied(userAuth).forApplication(appName); } } } // no auth granted return Authorization.denied(Authentication.ANONYMOUS); } private Authorization grantAnnonymous() { return Authorization.granted(Authentication.ANONYMOUS); } }

从上面的代码可以看出,其会先去获取engine的授权,如果没有的话,则直接返回并拒绝;
然后再从org.camunda.bpm.cockpit.Cockpit的类里面通过代理org.camunda.bpm.cockpit.impl.DefaultCockpitRuntimeDelegate 获取ProcessEngine的对象。
如果ProcessEngine返回的是空对象,则认为没有认证,并返回。

如果都有的话,则转换成为一个UserAuthentication的对象,并返回给上下文。

默认情况下是匿名的授权:

  public static class AnnonymousAuthorizer implements RequestAuthorizer {

    @Override
    public Authorization authorize(Map<String, String> parameters) {
      return Authorization.granted(Authentication.ANONYMOUS);
    }
  }

RequestAuthorizer.class是一个接口,其只有一个方法:

package org.camunda.bpm.webapp.impl.security.filter;

import java.util.Map;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;

/**
 * The interface for request authorizers.
 * @author nico.rehwaldt
 */
public interface RequestAuthorizer {

  public static final RequestAuthorizer AUTHORIZE_ANNONYMOUS = new AnnonymousAuthorizer();

  /**
   * Authorize a request with the given parameters by returning a valid {@link Authentication}.
   *
   * @param parameters
   *
   * @return a valid {@link Authentication} or null if authorization to this request
   *         has not been granted
   */
  public Authorization authorize(Map<String, String> parameters);

再回头来看看securityFilterRules.json是如何被应用的?其会被 org.camunda.bpm.webapp.impl.security.filter.PathFilterRule 所读取使用。


package org.camunda.bpm.webapp.impl.security.filter;

import java.util.ArrayList;
import java.util.List;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;
import org.camunda.bpm.webapp.impl.security.filter.RequestMatcher.Match;
import org.camunda.bpm.webapp.impl.security.filter.util.FilterRules;
import org.springframework.util.PathMatcher;

/**
 * 

A {@link SecurityFilterRule} that deleagates to a set of {@link PathMatcher}s

* *

How this thing works: *

    *
  • A path that is not listed in deniedPaths is always granted anonymous access * (even if the user is authenticated for a process engine). *
  • A path that is listed in deniedPaths is then also checked against allowedPaths. *
  • A path that is listed in allowedPaths is checked by the * corresponding {@link RequestAuthorizer} that can decide to grant/deny (identified or anonymous) access. *
  • A path that is not listed in allowedPaths is always granted anonymous access * (via {@link FilterRules#authorize(String, String, List)}) * * @author Daniel Meyer * @author nico.rehwaldt */ public class PathFilterRule implements SecurityFilterRule { protected List<RequestMatcher> allowedPaths = new ArrayList<>(); protected List<RequestMatcher> deniedPaths = new ArrayList<>(); @Override public Authorization authorize(String requestMethod, String requestUri) { boolean secured = false; for (RequestMatcher pattern : deniedPaths) { Match match = pattern.match(requestMethod, requestUri); if (match != null) { secured = true; break; } } if (!secured) { return Authorization.granted(Authentication.ANONYMOUS); } for (RequestMatcher pattern : allowedPaths) { Match match = pattern.match(requestMethod, requestUri); if (match != null) { return match.authorize(); } } return null; } public List<RequestMatcher> getAllowedPaths() { return allowedPaths; } public List<RequestMatcher> getDeniedPaths() { return deniedPaths; } }

PathFilterRule 会被org.camunda.bpm.webapp.impl.security.filter.util.FilterRules 引用,

package org.camunda.bpm.webapp.impl.security.filter.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.camunda.bpm.engine.impl.util.ReflectUtil;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;
import org.camunda.bpm.webapp.impl.security.filter.Authorization;
import org.camunda.bpm.webapp.impl.security.filter.PathFilterRule;
import org.camunda.bpm.webapp.impl.security.filter.RequestAuthorizer;
import org.camunda.bpm.webapp.impl.security.filter.RequestFilter;
import org.camunda.bpm.webapp.impl.security.filter.RequestMatcher;
import org.camunda.bpm.webapp.impl.security.filter.SecurityFilterConfig;
import org.camunda.bpm.webapp.impl.security.filter.SecurityFilterConfig.PathFilterConfig;
import org.camunda.bpm.webapp.impl.security.filter.SecurityFilterConfig.PathMatcherConfig;
import org.camunda.bpm.webapp.impl.security.filter.SecurityFilterRule;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Utility to load and match filter rules.
 *
 * @author nico.rehwaldt
 */
public class FilterRules {

  public static List<SecurityFilterRule> load(InputStream configFileResource,
                                              String applicationPath) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();

    SecurityFilterConfig config = objectMapper.readValue(configFileResource, SecurityFilterConfig.class);
    return createFilterRules(config, applicationPath);
  }

  public static List<SecurityFilterRule> createFilterRules(SecurityFilterConfig config,
                                                           String applicationPath) {
    PathFilterConfig pathFilter = config.getPathFilter();
    PathFilterRule rule = createPathFilterRule(pathFilter, applicationPath);

    return new ArrayList<>(Collections.singletonList(rule));
  }

  protected static PathFilterRule createPathFilterRule(PathFilterConfig pathFilter,
                                                       String applicationPath) {
    PathFilterRule pathFilterRule = new PathFilterRule();

    for (PathMatcherConfig pathMatcherConfig : pathFilter.getDeniedPaths()) {
      pathFilterRule.getDeniedPaths().add(transformPathMatcher(pathMatcherConfig, applicationPath));
    }

    for (PathMatcherConfig pathMatcherConfig : pathFilter.getAllowedPaths()) {
      pathFilterRule.getAllowedPaths().add(transformPathMatcher(pathMatcherConfig, applicationPath));
    }

    return pathFilterRule;
  }

  protected static RequestMatcher transformPathMatcher(PathMatcherConfig pathMatcherConfig,
                                                       String applicationPath) {
    RequestFilter requestMatcher = new RequestFilter(
        pathMatcherConfig.getPath(),
        applicationPath,
        pathMatcherConfig.getParsedMethods());

    RequestAuthorizer requestAuthorizer = RequestAuthorizer.AUTHORIZE_ANNONYMOUS;

    if (pathMatcherConfig.getAuthorizer() != null) {
      String authorizeCls = pathMatcherConfig.getAuthorizer();
      requestAuthorizer = (RequestAuthorizer) ReflectUtil.instantiate(authorizeCls);
    }

    return new RequestMatcher(requestMatcher, requestAuthorizer);
  }

  /**
   * Iterate over a number of filter rules and match them against
   * the given request.
   *
   * @param requestMethod
   * @param requestUri
   * @param filterRules
   *
   * @return the checked request with authorization information attached
   */
  public static Authorization authorize(String requestMethod, String requestUri, List<SecurityFilterRule> filterRules) {

    Authorization authorization;

    for (SecurityFilterRule filterRule : filterRules) {
      authorization = filterRule.authorize(requestMethod, requestUri);

      if (authorization != null) {
        return authorization;
      }
    }

    // grant if no filter disallows it
    return Authorization.granted(Authentication.ANONYMOUS);
  }
}

而org.camunda.bpm.webapp.impl.security.filter.util.FilterRules会被下面的org.camunda.bpm.webapp.impl.security.filter.SecurityFilter 引用;

/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; 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.camunda.bpm.webapp.impl.security.filter;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.camunda.bpm.engine.impl.util.IoUtil;
import org.camunda.bpm.webapp.impl.util.ServletContextUtil;
import org.camunda.bpm.webapp.impl.security.auth.Authentications;
import org.camunda.bpm.webapp.impl.security.filter.util.FilterRules;


/**
 * 

Simple filter implementation which delegates to a list of {@link SecurityFilterRule FilterRules}, * evaluating their {@link SecurityFilterRule#setAuthorized(org.camunda.bpm.webapp.impl.security.filter.AppRequest)} condition * for the given request.

* *

This filter must be configured using a init-param in the web.xml file. The parameter must be named * "configFile" and point to the configuration file located in the servlet context.

* * @author Daniel Meyer * @author nico.rehwaldt */
public class SecurityFilter implements Filter { public List<SecurityFilterRule> filterRules = new ArrayList<SecurityFilterRule>(); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilterSecure((HttpServletRequest) request, (HttpServletResponse) response, chain); } public void doFilterSecure(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String requestUri = getRequestUri(request); Authorization authorization = authorize(request.getMethod(), requestUri, filterRules); // attach authorization headers // to response authorization.attachHeaders(response); if (authorization.isGranted()) { // if request is authorized chain.doFilter(request, response); } else if (authorization.isAuthenticated()) { String application = authorization.getApplication(); if (application != null) { sendForbiddenApplicationAccess(application, request, response); } else { sendForbidden(request, response); } } else { sendUnauthorized(request, response); } } @Override public void init(FilterConfig filterConfig) throws ServletException { ServletContext servletContext = filterConfig.getServletContext(); String applicationPath = ServletContextUtil.getAppPath(servletContext); loadFilterRules(filterConfig, applicationPath); } @Override public void destroy() { } /** * Iterate over a number of filter rules and match them against * the specified request. * * @param request * @param filterRules * * @return the joined {@link AuthorizationStatus} for this request matched against all filter rules */ public static Authorization authorize(String requestMethod, String requestUri, List<SecurityFilterRule> filterRules) { return FilterRules.authorize(requestMethod, requestUri, filterRules); } protected void loadFilterRules(FilterConfig filterConfig, String applicationPath) throws ServletException { String configFileName = filterConfig.getInitParameter("configFile"); InputStream configFileResource = filterConfig.getServletContext().getResourceAsStream(configFileName); if (configFileResource == null) { throw new ServletException("Could not read security filter config file '"+configFileName+"': no such resource in servlet context."); } else { try { filterRules = FilterRules.load(configFileResource, applicationPath); } catch (Exception e) { throw new RuntimeException("Exception while parsing '" + configFileName + "'", e); } finally { IoUtil.closeSilently(configFileResource); } } } protected void sendForbidden(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(403); } protected void sendUnauthorized(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(401); } protected void sendForbiddenApplicationAccess(String application, HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(403, "No access rights for " + application); } protected boolean isAuthenticated(HttpServletRequest request) { return Authentications.getCurrent() != null; } protected String getRequestUri(HttpServletRequest request) { String contextPath = request.getContextPath(); return request.getRequestURI().substring(contextPath.length()); } }

而SecurityFilter作为一个父类,其会被org.camunda.bpm.spring.boot.starter.webapp.filter.ResourceLoadingSecurityFilter集成,

package org.camunda.bpm.spring.boot.starter.webapp.filter;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;

import org.camunda.bpm.engine.impl.util.IoUtil;
import org.camunda.bpm.spring.boot.starter.property.WebappProperty;
import org.camunda.bpm.webapp.impl.security.filter.SecurityFilter;
import org.camunda.bpm.webapp.impl.security.filter.util.FilterRules;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class ResourceLoadingSecurityFilter extends SecurityFilter implements ResourceLoaderDependingFilter {

  private ResourceLoader resourceLoader;

  private WebappProperty webappProperty;
  @Override
  protected void loadFilterRules(FilterConfig filterConfig, String applicationPath) throws ServletException {
    String configFileName = filterConfig.getInitParameter("configFile");
    Resource resource = resourceLoader.getResource("classpath:" +webappProperty.getWebjarClasspath() + configFileName);
    InputStream configFileResource;
    try {
      configFileResource = resource.getInputStream();
    } catch (IOException e1) {
      throw new ServletException("Could not read security filter config file '" + configFileName + "': no such resource in servlet context.");
    }
    try {
      filterRules = FilterRules.load(configFileResource, applicationPath);
    } catch (Exception e) {
      throw new RuntimeException("Exception while parsing '" + configFileName + "'", e);
    } finally {
      IoUtil.closeSilently(configFileResource);
    }
  }
  
  /**
   * @return the resourceLoader
   */
  public ResourceLoader getResourceLoader() {
    return resourceLoader;
  }

  /**
   * @param resourceLoader
   *          the resourceLoader to set
   */
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
  
  /**
   * @return the webappProperty
   */
    public WebappProperty getWebappProperty() {
        return webappProperty;
    }

    /**
     * @param webappProperty 
     *          webappProperty to set
     */
    public void setWebappProperty(WebappProperty webappProperty) {
        this.webappProperty = webappProperty;
    }
  
}

而org.camunda.bpm.spring.boot.starter.webapp.filter.ResourceLoadingSecurityFilter 又会被org.camunda.bpm.spring.boot.starter.webapp.filter.LazySecurityFilter 加载;

而LazySecurityFilter 又会被org.camunda.bpm.spring.boot.starter.webapp.CamundaBpmWebappInitializer 初始化。

package org.camunda.bpm.spring.boot.starter.webapp;

import org.camunda.bpm.admin.impl.web.AdminApplication;
import org.camunda.bpm.admin.impl.web.bootstrap.AdminContainerBootstrap;
import org.camunda.bpm.cockpit.impl.web.CockpitApplication;
import org.camunda.bpm.cockpit.impl.web.bootstrap.CockpitContainerBootstrap;
import org.camunda.bpm.engine.rest.filter.CacheControlFilter;
import org.camunda.bpm.engine.rest.filter.EmptyBodyFilter;
import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties;
import org.camunda.bpm.spring.boot.starter.property.WebappProperty;
import org.camunda.bpm.spring.boot.starter.webapp.filter.LazyProcessEnginesFilter;
import org.camunda.bpm.spring.boot.starter.webapp.filter.LazySecurityFilter;
import org.camunda.bpm.tasklist.impl.web.TasklistApplication;
import org.camunda.bpm.tasklist.impl.web.bootstrap.TasklistContainerBootstrap;
import org.camunda.bpm.webapp.impl.engine.EngineRestApplication;
import org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter;
import org.camunda.bpm.webapp.impl.security.filter.CsrfPreventionFilter;
import org.camunda.bpm.webapp.impl.security.filter.headersec.HttpHeaderSecurityFilter;
import org.camunda.bpm.webapp.impl.security.filter.util.HttpSessionMutexListener;
import org.camunda.bpm.webapp.impl.util.ServletContextUtil;
import org.camunda.bpm.welcome.impl.web.WelcomeApplication;
import org.camunda.bpm.welcome.impl.web.bootstrap.WelcomeContainerBootstrap;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.SessionTrackingMode;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;

import static java.util.Collections.singletonMap;
import static org.glassfish.jersey.servlet.ServletProperties.JAXRS_APPLICATION_CLASS;

/**
 * Inspired by:
 * https://groups.google.com/forum/#!msg/camunda-bpm-users/BQHdcLIivzs
 * /iNVix8GkhYAJ (Christoph Berg)
 */
public class CamundaBpmWebappInitializer implements ServletContextInitializer {

  private static final Logger log = LoggerFactory.getLogger(CamundaBpmWebappInitializer.class);

  private static final EnumSet<DispatcherType> DISPATCHER_TYPES = EnumSet.of(DispatcherType.REQUEST);

  private ServletContext servletContext;

  private final CamundaBpmProperties properties;

  public CamundaBpmWebappInitializer(CamundaBpmProperties properties) {
    this.properties = properties;
  }

  @Override
  public void onStartup(ServletContext servletContext) {
    this.servletContext = servletContext;

    servletContext.setSessionTrackingModes(Collections.singleton(SessionTrackingMode.COOKIE));

    servletContext.addListener(new CockpitContainerBootstrap());
    servletContext.addListener(new AdminContainerBootstrap());
    servletContext.addListener(new TasklistContainerBootstrap());
    servletContext.addListener(new WelcomeContainerBootstrap());
    servletContext.addListener(new HttpSessionMutexListener());

    WebappProperty webapp = properties.getWebapp();
    String applicationPath = webapp.getApplicationPath();

    ServletContextUtil.setAppPath(applicationPath, servletContext);

    registerFilter("Authentication Filter", AuthenticationFilter.class,
        applicationPath + "/api/*", applicationPath + "/app/*");
    registerFilter("Security Filter", LazySecurityFilter.class,
        singletonMap("configFile", webapp.getSecurityConfigFile()),
        applicationPath + "/api/*", applicationPath + "/app/*");
    registerFilter("CsrfPreventionFilter", CsrfPreventionFilter.class,
        webapp.getCsrf().getInitParams(),
        applicationPath + "/api/*", applicationPath + "/app/*");

    Map<String, String> headerSecurityProperties = webapp
      .getHeaderSecurity()
      .getInitParams();

    registerFilter("HttpHeaderSecurity", HttpHeaderSecurityFilter.class,
        headerSecurityProperties,
        applicationPath + "/api/*", applicationPath + "/app/*");

    registerFilter("Engines Filter", LazyProcessEnginesFilter.class,
        applicationPath + "/api/*", applicationPath + "/app/*");

    registerFilter("EmptyBodyFilter", EmptyBodyFilter.class,
        applicationPath + "/api/*", applicationPath + "/app/*");

    registerFilter("CacheControlFilter", CacheControlFilter.class,
        applicationPath + "/api/*", applicationPath + "/app/*");

    registerServlet("Cockpit Api", CockpitApplication.class,
        applicationPath + "/api/cockpit/*");
    registerServlet("Admin Api", AdminApplication.class,
        applicationPath + "/api/admin/*");
    registerServlet("Tasklist Api", TasklistApplication.class,
        applicationPath + "/api/tasklist/*");
    registerServlet("Engine Api", EngineRestApplication.class,
        applicationPath + "/api/engine/*");
    registerServlet("Welcome Api", WelcomeApplication.class,
        applicationPath + "/api/welcome/*");
  }

  private FilterRegistration registerFilter(final String filterName, final Class<? extends Filter> filterClass, final String... urlPatterns) {
    return registerFilter(filterName, filterClass, null, urlPatterns);
  }

  private FilterRegistration registerFilter(final String filterName, final Class<? extends Filter> filterClass, final Map<String, String> initParameters,
                                            final String... urlPatterns) {
    FilterRegistration filterRegistration = servletContext.getFilterRegistration(filterName);

    if (filterRegistration == null) {
      filterRegistration = servletContext.addFilter(filterName, filterClass);
      filterRegistration.addMappingForUrlPatterns(DISPATCHER_TYPES, true, urlPatterns);

      if (initParameters != null) {
        filterRegistration.setInitParameters(initParameters);
      }

      log.debug("Filter {} for URL {} registered.", filterName, urlPatterns);
    }

    return filterRegistration;
  }

  private ServletRegistration registerServlet(final String servletName, final Class<?> applicationClass, final String... urlPatterns) {
    ServletRegistration servletRegistration = servletContext.getServletRegistration(servletName);

    if (servletRegistration == null) {
      servletRegistration = servletContext.addServlet(servletName, ServletContainer.class);
      servletRegistration.addMapping(urlPatterns);
      servletRegistration.setInitParameters(singletonMap(JAXRS_APPLICATION_CLASS, applicationClass.getName()));

      log.debug("Servlet {} for URL {} registered.", servletName, urlPatterns);
    }

    return servletRegistration;
  }
}

org.camunda.bpm.spring.boot.starter.webapp.CamundaBpmWebappInitializer会在 org.camunda.bpm.spring.boot.starter.webapp.CamundaBpmWebappAutoConfiguration进行初始化Bean,其读取的是 org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties properties;

/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; 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.camunda.bpm.spring.boot.starter.webapp;

import org.camunda.bpm.spring.boot.starter.CamundaBpmAutoConfiguration;
import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties;
import org.camunda.bpm.spring.boot.starter.property.WebappProperty;
import org.camunda.bpm.spring.boot.starter.webapp.filter.LazyDelegateFilter.InitHook;
import org.camunda.bpm.spring.boot.starter.webapp.filter.LazyInitRegistration;
import org.camunda.bpm.spring.boot.starter.webapp.filter.ResourceLoaderDependingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnProperty(prefix = WebappProperty.PREFIX, name = "enabled", matchIfMissing = true)
@ConditionalOnBean(CamundaBpmProperties.class)
@ConditionalOnWebApplication
@AutoConfigureAfter(CamundaBpmAutoConfiguration.class)
public class CamundaBpmWebappAutoConfiguration implements WebMvcConfigurer {

  @Autowired
  private ResourceLoader resourceLoader;

  @Autowired
  private CamundaBpmProperties properties;


  @Bean
  public CamundaBpmWebappInitializer camundaBpmWebappInitializer() {
    return new CamundaBpmWebappInitializer(properties);
  }

  @Bean(name = "resourceLoaderDependingInitHook")
  public InitHook<ResourceLoaderDependingFilter> resourceLoaderDependingInitHook() {
    return filter -> {
      filter.setResourceLoader(resourceLoader);
      filter.setWebappProperty(properties.getWebapp());
    };
  }

  @Bean
  public LazyInitRegistration lazyInitRegistration() {
    return new LazyInitRegistration();
  }

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    final String classpath = "classpath:" + properties.getWebapp().getWebjarClasspath();
    WebappProperty webapp = properties.getWebapp();
    String applicationPath = webapp.getApplicationPath();

    registry.addResourceHandler(applicationPath + "/lib/**")
        .addResourceLocations(classpath + "/lib/");
    registry.addResourceHandler(applicationPath + "/api/**")
        .addResourceLocations("classpath:/api/");
    registry.addResourceHandler(applicationPath + "/app/**")
        .addResourceLocations(classpath + "/app/");
  }

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    WebappProperty webapp = properties.getWebapp();
    if (webapp.isIndexRedirectEnabled()) {
      String applicationPath = webapp.getApplicationPath();
      registry.addRedirectViewController("/", applicationPath + "/app/");
    }
  }

}

而上面的CamundaBpmWebappAutoConfiguration的这个类其实是包括在
org.camunda.bpm.springboot:camunda-bpm-spring-boot-starter-webapp-core这个组件当中。
其代码结构如下:

org.camunda.bpm.springboot:camunda-bpm-spring-boot-starter-webapp-core这个组件当又会被
org.camunda.bpm.springboot:camunda-bpm-spring-boot-starter-webapp 组件引用。

下面从调用顺序整理的一下:

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/757990.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-01
下一篇 2022-05-01

发表评论

登录后才能评论

评论列表(0条)

保存