camunda系列教程2(集成实践)

工作流 同时被 3 个专栏收录
12 篇文章 1 订阅
8 篇文章 1 订阅
9 篇文章 0 订阅

目录

snowy开源版本拉取:

springboot与camunda版本对比:

camunda7.15版本相关依赖,引入snowy-main中的pom文件:

snowy父pom将spring-boot-starter-parent版本进行(选择性)修改为2.4.3

yaml camunda的账户配置(选择性)

SnowyApplication启动类进行修改,添加camunda注解@EnableProcessApplication

将camunda接口相关权限放开SpringSecurityConstant:

启动后端,访问地址:http://localhost:82/camunda/app/welcome/default/#!/login

接管小诺放行的请求,开启camunda的请求拦截(鉴权):

sso自动登录

sso前后端密文传输

自动登录测试

对camunda的鉴权进行自定义(选择性使用)

放行camunda请求的,请求头校验

camunda鉴权开启效果

 camunda web页面开启汉化

嵌入式表单位置:/resources/static/forms


  • snowy开源版本拉取:

Snowy: 💖Snowy基于SpringBoot+AntDesignVue的前后分离全新RBAC权限管理系统,适配国产数据库(金仓、达梦)、主流数据库Mysql、Oracle、Mssql、Postgresql,小诺一致追求简洁干净,一套代码搞定!支持国产中间件部署、麒麟操作系统、Windows、Linux部署使用,另外支持saas多租户、flowable工作流、多数据源、支付模块等,更多插件正在扩展中。https://gitee.com/xiaonuobase/snowy

  • springboot与camunda版本对比:

Spring Boot Version Compatibility | docs.camunda.orgdocumentation of the Camunda Platformhttps://docs.camunda.org/manual/7.15/user-guide/spring-boot-integration/version-compatibility/

  • camunda7.15版本相关依赖,引入snowy-main中的pom文件:

        <!--camunda的依赖包start-->
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
            <version>7.15.0</version>
        </dependency>

        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
            <version>7.15.0</version>
        </dependency>

        <dependency>
            <groupId>org.camunda.bpm</groupId>
            <artifactId>camunda-engine-plugin-spin</artifactId>
            <version>7.15.0</version>
        </dependency>

        <dependency>
            <groupId>org.camunda.spin</groupId>
            <artifactId>camunda-spin-dataformat-all</artifactId>
            <version>1.11.0</version>
        </dependency>
        <!--camunda的依赖包end-->
  • snowy父pom将spring-boot-starter-parent版本进行(选择性)修改为2.4.3

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
<!--        <version>2.3.1.RELEASE</version>-->
        <version>2.4.3</version>
    </parent>
  • yaml camunda的账户配置(选择性)

如果不进行配置,那么在你登录的camunda官方提供的web页面时,camunda也会要求你初始化一个超级管理员的账户

camunda.bpm.admin-user:
  id: superAdmin
  password: 123456
  • SnowyApplication启动类进行修改,添加camunda注解@EnableProcessApplication

// 防止过滤器创建报空指针异常(2-1)(springSecurityConstant需要放开路径:"/camunda/api/engine")
@EnableProcessApplication
@SpringBootApplication
public class SnowyApplication {……}
  • 将camunda接口相关权限放开SpringSecurityConstant:

public interface SpringSecurityConstant {

    /**
     * 放开权限校验的接口
     */
    String[] NONE_SECURITY_URL_PATTERNS = {

            ……

            // modeler 流程部署接口
            "/engine-rest/**",
            // modeler 表单部署
            "/forms/**",
            // camunda工作流(web)
            "/camunda/**",
            // 防止过滤器创建报空指针异常(2-2)(启动类需要启用:@EnableProcessApplication)
            "/camunda/api/engine"

    };

}

  • 接管小诺放行的请求,开启camunda的请求拦截(鉴权):

package vip.xiaonuo.camunda.common.auth.filter;

import org.camunda.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 开启鉴权后,camunda的web请求(包含流程设计器),会在请求头中携带以Basic开头的authorization请求头,
 * 此时要确保自己的系统不会拦截以Basic开头的请求头
 *
 * @author 每天一点
 */
@Configuration
public class CamundaSecurityFilter {
    @Bean
    public FilterRegistrationBean<ProcessEngineAuthenticationFilter> processEngineAuthenticationFilter() {
        FilterRegistrationBean<ProcessEngineAuthenticationFilter> registration = new FilterRegistrationBean<>(new ProcessEngineAuthenticationFilter());
//        registration.setName("camunda-auth-api");
//        registration.setFilter(new ProcessEngineAuthenticationFilter());
        // camunda官方提供的鉴权(推荐)
        registration.addInitParameter("authentication-provider",
                "org.camunda.bpm.engine.rest.security.auth.impl.HttpBasicAuthenticationProvider");
        // 根据camunda官方提供的鉴权进行改写(如果自己的系统不需要访问官方的接口,那么推荐使用camunda的鉴权)
//        processEngineAuthenticationFilterRegistrationBean.addInitParameter("authentication-provider",
//                "vip.xiaonuo.camunda.common.auth.provider.CamundaAuthenticationProvider");
        registration.addUrlPatterns("/engine-rest/*");
        registration.setOrder(2);
        return registration;
    }
}
  • sso自动登录

package vip.xiaonuo.camunda.common.auth.config;

import org.camunda.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter;
import org.camunda.bpm.engine.rest.security.auth.impl.HttpBasicAuthenticationProvider;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import vip.xiaonuo.camunda.common.auth.filter.AutoLoginAuthenticationFilter;

/**
 * 开启鉴权后,camunda的web请求(包含流程设计器),会在请求头中携带以Basic开头的authorization请求头,
 * 此时要确保自己的系统不会拦截以Basic开头的请求头
 *
 * @author 每天一点
 */
@Configuration
public class CamundaSecurityConfig {
    // ContainerBasedAuthenticationFilter、ContainerBasedAuthenticationProvider
    @Bean
    public FilterRegistrationBean<AutoLoginAuthenticationFilter> containerBasedAuthenticationFilter() {
        FilterRegistrationBean<AutoLoginAuthenticationFilter> registration = new FilterRegistrationBean<>();
        registration.setName("camunda-auth-web");
        registration.setFilter(new AutoLoginAuthenticationFilter());
        registration.addUrlPatterns("/camunda/app/*" , "/camunda/api/*");
        registration.setOrder(1);
        return registration;
    }
    @Bean
    public FilterRegistrationBean<ProcessEngineAuthenticationFilter> processEngineAuthenticationFilter() {
    ……………………
    }
}
package vip.xiaonuo.camunda.common.auth.filter;
import static org.camunda.bpm.engine.authorization.Permissions.ACCESS;
import static org.camunda.bpm.engine.authorization.Resources.APPLICATION;

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

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

import org.camunda.bpm.BpmPlatform;
import org.camunda.bpm.engine.AuthorizationService;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.identity.Group;
import org.camunda.bpm.engine.identity.Tenant;
import org.camunda.bpm.webapp.impl.security.SecurityActions;
import org.camunda.bpm.webapp.impl.security.SecurityActions.SecurityAction;
import org.camunda.bpm.webapp.impl.security.auth.Authentication;
import org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter;
import org.camunda.bpm.webapp.impl.security.auth.Authentications;
import org.camunda.bpm.webapp.impl.security.auth.UserAuthentication;

/**
 * Replaces {@link AuthenticationFilter} by an extension which can auto-logon a
 * user by given username as URL parameter, e.g.
 *
 * http://localhost:82/camunda/?auto-login-username=admin
 *
 * 任务列表
 * http://localhost:82/camunda/app/tasklist/?auto-login-username=admin
 * cockpit
 * http://localhost:82/camunda/app/cockpit/?auto-login-username=admin
 * tasklist
 * http://localhost:82/camunda/app/tasklist/?auto-login-username=admin
 *
 * Of course this IS A SECURITY ISSUE! It is only meant to be used in test
 * environments
 *
 * @author 每天一点
 */
public class AutoLoginAuthenticationFilter implements Filter {

    private static final String[] APPS = new String[] { "cockpit", "tasklist", "admin", "welcome" };

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }
    @Override
    public void destroy() {
    }
    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest req = (HttpServletRequest) request;

        // get authentication from session
        Authentications authentications = Authentications.getFromSession(req.getSession());

        // This function is added to the normal AuthenticationFilter
        setAutoLoginAuthentication(request, authentications);

        // set current authentication to the one restored from session (maybe
        // auto login was added)
        Authentications.setCurrent(authentications);

        try {

            SecurityActions.runWithAuthentications(new SecurityAction<Void>() {
                @Override
                public Void execute() {
                    try {
                        chain.doFilter(request, response);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    return null;
                }
            }, authentications);
        } finally {
            Authentications.clearCurrent();
            // store updated authentication object in session for next request
            Authentications.updateSession(req.getSession(), authentications);
        }
    }

    /**
     * Reads the auto-login-username from the URL parameters and create an
     * {@link Authentication} for it containing its groups, tenants and
     * authorized apps.
     *
     * No password check is done here, so you can log onto every user without a
     * password. Only makes sense in demo environments!
     */
    protected void setAutoLoginAuthentication(final ServletRequest request, Authentications authentications) {
        final HttpServletRequest req = (HttpServletRequest) request;
        final ProcessEngine engine = getEngine();

        // Get the username from the user in SSO
        String username = retrieveUsername(req);

        // if not set - no auto login
        if (username == null) {
            return;
        }

        // if already in the list of logged in users - nothing to do
        Authentication authentication = authentications.getAuthenticationForProcessEngine(engine.getName());
        if (authentication != null && authentication.getName() == username) {
            return;
        }

        AuthorizationService authorizationService = engine.getAuthorizationService();

        // query group information
        List<String> groupIds = getGroupsOfUser(engine, username);
        List<String> tenantIds = getTenantsOfUser(engine, username);

        // check user's app authorizations by iterating of list of apps and ask
        // if permitted
        HashSet<String> authorizedApps = new HashSet<String>();
        authorizedApps.add("admin");
        if (engine.getProcessEngineConfiguration().isAuthorizationEnabled()) {
            for (String application : APPS) {
                if (authorizationService.isUserAuthorized(username, groupIds, ACCESS, APPLICATION, application)) {
                    authorizedApps.add(application);
                }
            }
        } else {
            Collections.addAll(authorizedApps, APPS);
        }

        // create new authentication object to store authentication
        UserAuthentication newAuthentication = new UserAuthentication(username, engine.getName());
        newAuthentication.setGroupIds(groupIds);
        newAuthentication.setTenantIds(tenantIds);
        newAuthentication.setAuthorizedApps(authorizedApps);

        // and add the new logged in user
        authentications.addAuthentication(newAuthentication);
    }

    /**
     * Get the username - this is different based on the SSO technology used,
     * see e.g.
     * https://github.com/camunda/camunda-sso-jboss/blob/master/camunda-bpm-
     * build-webapp/src/main/java/de/novatec/bpm/webapp/impl/security/auth/
     * AuthenticationFilter.java#L57 for an implementation using Kerberos on
     * JBoss AS 7
     */
    protected String retrieveUsername(final HttpServletRequest req) {
        // Simply read it from a URL parameter in this case
        return req.getParameter("auto-login-username");
    }

    /**
     * copied from
     * org.camunda.bpm.webapp.impl.security.auth.UserAuthenticationResource
     */
    protected List<String> getGroupsOfUser(ProcessEngine engine, String userId) {
        List<Group> groups = engine.getIdentityService().createGroupQuery().groupMember(userId).list();

        List<String> groupIds = new ArrayList<String>();
        for (Group group : groups) {
            groupIds.add(group.getId());
        }
        return groupIds;
    }

    protected List<String> getTenantsOfUser(ProcessEngine engine, String userId) {
        List<Tenant> tenants = engine.getIdentityService().createTenantQuery().userMember(userId).includingGroupsOfUser(true).list();

        List<String> tenantIds = new ArrayList<String>();
        for (Tenant tenant : tenants) {
            tenantIds.add(tenant.getId());
        }
        return tenantIds;
    }

    private ProcessEngine getEngine() {
        // TODO: Only works in single engine environment!
        return BpmPlatform.getDefaultProcessEngine();
    }

}
  • sso前后端密文传输

base64加密(时间戳+';'+用户名)

  • 后端解密:修改retrieveUsername方法
protected String retrieveUsername(final HttpServletRequest req) {
        // Simply read it from a URL parameter in this case
        String parameter = req.getParameter("auto-login-username");
        if(ObjectUtil.isEmpty(parameter)){
            return null;
        }
        String decodedCredentials = new String(Base64.decodeBase64(parameter));
        int firstColonIndex = decodedCredentials.indexOf(":");
        if (firstColonIndex == -1) {
            return null;
        }else {
            String timeStamp = decodedCredentials.substring(0, firstColonIndex);
            long timeDelta = System.currentTimeMillis() - Long.parseLong(timeStamp);
            if(timeDelta < 0 || timeDelta > 100000){
                return null;
            }
            return decodedCredentials.substring(firstColonIndex + 1);
        }
    }
  • 前端加密

<template>
  <div>
    <a-button type="primary" @click="welcomeClick">
      welcome
    </a-button>
    <!--    <iframe id="iframe" height="580px" :src="srcUrl" style="width:100%;overflow:hidden;" frameBorder="0">-->
    <!--    </iframe>-->
  </div>
</template>

<script>

const Base64 = require('js-base64').Base64
export default {
  name: 'Index',
  data() {
    return {
      srcUrl: ''
    }
  },
  mounted() {
    // this.srcUrl = process.env.VUE_APP_API_BASE_URL + '/camunda/app/welcome/?auto-login-username=admin'
  },
  methods: {
    welcomeClick() {
      const param = Base64.encode((new Date().getTime() + ':admin'))
      window.open(process.env.VUE_APP_API_BASE_URL + '/camunda/app/welcome/?auto-login-username=' + param, '_blank')
    }
  }
}
</script>

<style scoped>

</style>
  • 自动登录测试

http://localhost:82/camunda/app/welcome/?auto-login-username=admin

  • 对camunda的鉴权进行自定义(选择性使用)

如果自己的系统要直接访问camunda官方提供的接口,那么就需要对camunda的鉴权进行自定义,可参考以下内容:

package vip.xiaonuo.camunda.common.auth.provider;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.identity.User;
import org.camunda.bpm.engine.impl.digest._apacheCommonsCodec.Base64;
import org.camunda.bpm.engine.rest.security.auth.AuthenticationProvider;
import org.camunda.bpm.engine.rest.security.auth.AuthenticationResult;
import org.springframework.util.StringUtils;
import vip.xiaonuo.core.context.login.LoginContextHolder;
import vip.xiaonuo.core.pojo.login.SysLoginUser;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;


/**
 * 如果想在自己的系统中访问camunda官方提供的接口,那么就需要对鉴权部分进行重写
 * @author 每天一点
 */
public class CamundaAuthenticationProvider implements AuthenticationProvider {
    protected static final String BASIC_AUTH_HEADER_PREFIX = "Basic ";
    protected static final String TOKEN_TYPE_BEARER = "Bearer";
    @Override
    public AuthenticationResult extractAuthenticatedUser(HttpServletRequest request, ProcessEngine engine) {
        String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (authorizationHeader != null) {
            if(authorizationHeader.startsWith(BASIC_AUTH_HEADER_PREFIX)){
                String encodedCredentials = authorizationHeader.substring(BASIC_AUTH_HEADER_PREFIX.length());
                String decodedCredentials = new String(Base64.decodeBase64(encodedCredentials));
                int firstColonIndex = decodedCredentials.indexOf(":");
                if (firstColonIndex != -1) {
                    String userName = decodedCredentials.substring(0, firstColonIndex);
                    String password = decodedCredentials.substring(firstColonIndex + 1);
                    if (isAuthenticated(engine, userName, password)) {
                        return AuthenticationResult.successful(userName);
                    }
                }
            }
            if(authorizationHeader.startsWith(TOKEN_TYPE_BEARER)){
                SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUser();
                // AuthService authService = SpringUtil.getBean(AuthService.class);
                User user = engine.getIdentityService().createUserQuery().userId(sysLoginUser.getAccount()).singleResult();
                if(!StringUtils.isEmpty(user)){
                    return AuthenticationResult.successful(user.getId());
                }
            }
        }
        return AuthenticationResult.unsuccessful();
    }
    protected boolean isAuthenticated(ProcessEngine engine, String userName, String password) {
        return engine.getIdentityService().checkPassword(userName, password);
    }
    @Override
    public void augmentResponseByAuthenticationChallenge(
            HttpServletResponse response, ProcessEngine engine) {
        response.setHeader(HttpHeaders.WWW_AUTHENTICATE, BASIC_AUTH_HEADER_PREFIX + "realm=\"" + engine.getName() + "\"");
    }

}
  • 放行camunda请求的,请求头校验

camunda开启鉴权后,官方的web请求会在请求头中携带authorization且以Basic作为开头,目前很多国内的系统,在鉴权的过程中,大部分都是先判断请求头中的是否含有authorization,如果有那么就判断是头是以Bearer作为开头,如果不是的话,那么就认为该请求头不符合规范,返回给前端;所以要想camunda的接口能够正常使用,那么就需要对框架中的这部分逻辑进行改进,以小诺框架为例,可参考以下内容:

注意这是一个坑:camunda的流程设计器,在鉴权未开启时,请求头中的authorization也会携带内容传递给后端,此时携带的内容为“undefined”字符串,针对这种情况,后端在针对这种情况时,也要进行放行

    @Override
    public String getTokenFromRequest(HttpServletRequest request) {
        String authToken = request.getHeader(CommonConstant.AUTHORIZATION);
        // 放行camunda以Basic开头的请求头
        if (ObjectUtil.isEmpty(authToken) || CommonConstant.UNDEFINED.equals(authToken)||authToken.startsWith(CommonConstant.TOKEN_TYPE_BASIC)) {
            return null;
        } else {
            //token不是以Bearer打头,则响应回格式不正确
            if (!authToken.startsWith(CommonConstant.TOKEN_TYPE_BEARER)) {
                throw new AuthException(AuthExceptionEnum.NOT_VALID_TOKEN_TYPE);
            }
            try {
                authToken = authToken.substring(CommonConstant.TOKEN_TYPE_BEARER.length() + 1);
            } catch (StringIndexOutOfBoundsException e) {
                throw new AuthException(AuthExceptionEnum.NOT_VALID_TOKEN_TYPE);
            }
        }

        return authToken;
    }
  • camunda鉴权开启效果

  • 流程设计器发生的变化:

Download The Camunda BPMN / DMN Process Modeler | CamundaA free and easy-to-use desktop app for editing BPMN Process Diagrams, DMN Decision Tables, and Forms. Camunda Modeler supports BPMN 2.0 and DMN 1.3.https://camunda.com/download/modeler/部署请求地址:http://localhost:82/engine-rest

  • camunda官方web发生的变化:

 此时再访问后端的接口在未登录的情况下访问:http://localhost:82/camunda/api/engine/engine/default/task/43483a51-2524-11ec-9fe5-861b7790c7c0

会出现如下效果:

  •  camunda web页面开启汉化

camunda7.15版本汉化_camunda汉化-企业管理文档类资源-CSDN下载camunda7.15版本汉化camunda汉化更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/weixin_44213308/21481004目录结构如下:

除了cockpit,admin和task,都汉化过了,注意:汉化区分版本7.14版本的汉化过程与7.15版本是有区别的,这是7.15版本的汉化过程。

效果如下:

  • 嵌入式表单位置:/resources/static/forms

具体使用方式,参考:

camunda集成系列教程3-嵌入式表单_<每天一点>的博客-CSDN博客https://docs.camunda.org/manual/latest/user-guide/task-forms/#camunda-forms首先,camunda平台的表单分为三种:1、嵌入式任务表单允许您将自定义 HTML 和 JavaScript 表单嵌入到任务列表中。2、Camunda Forms在 Camunda Modeler 中提供表单的可视化编辑,可用于不太复杂的表单。3、外部任务表单可用于链接到自定义应用程序。表单不会嵌入到任务列表中。首先在了解表单之前,我们先了https://blog.csdn.net/weixin_44213308/article/details/119715559

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:护眼 设计师:闪电赇 返回首页

打赏作者

<每天一点>

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值