本文的背景是基于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 组件引用。
下面从调用顺序整理的一下:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)