• Camunda工作流平台与Keycloak的集成


    Camunda是一个流行的工作流平台,其自带了基本的用户管理功能。Keycloak是业界主流的一个提供OAUTH等协议标准的一个用户验证与授权的平台。这里介绍如何把Camunda与Keycloak相集成,以实现通过Keycloak来统一管理用户的鉴权与授权,用户通过从Keycloak获取Token来调用Camunda的API。这篇文章也是参考了Github的这个仓库来写的,并经过实际测试有效:GitHub - camunda-community-hub/camunda-platform-7-keycloak: Camunda Keycloak Identity Provider Plugin

    Keycloak的设置

    这里采用Keycloak+PG数据库的方式来启动。PG数据库是和Camunda共用的。通过以下Docker命令来启动:

    docker run --name pg12 -v /home/roy/data/pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:12

    这里采用Docker的方式来启动Keycloak,命令如下:

    docker run -p 9090:8080 --name keycloak --link pg12 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=XXXXX quay.io/keycloak/keycloak:18.0.0 start-dev --db-url jdbc:postgresql://pg12:5432/keycloak --db-username postgres --db-password postgres --db=postgres

    运行之后就可以访问localhost:9090来进入到Keycloak的设置了。

    在Keycloak里面新建一个名为camunda的realm,然后在clients里面新建一个名为camunda-identity-service的client,配置如下:

     为了能使用refresh token,需要在OpenID Connect Compatibility Modes里面开启选项Use Refresh Tokens For Client Credentials Grant, 如下图:

     在Service Account Roles里面添加query-groups, query-users, view-users这3个Role,如下图:

     在这个camunda的realm里面新建一个用户组camunda-admin,如下图:

    Keycloak的设置完成之后,我们就可以用Springboot来集成Camunda了

    集成Springboot与Camunda

    首先我们需要新建一个Springboot的应用,集成Camunda。具体可以参照我之前的文章:Springboot集成Camunda流程引擎_gzroy的博客-CSDN博客 

    添加Keycloak插件

    在pom.xml文件里面增加以下依赖:

    1. <dependency>
    2. <groupId>org.camunda.bpm.extensiongroupId>
    3. <artifactId>camunda-platform-7-keycloakartifactId>
    4. <version>${camunda.spring-boot.version}version>
    5. dependency>

    在src目录新建一个plugin的目录,里面新建一个KeycloakIdentityProvider.java,代码如下:

    1. package com.roy.camunda.plugin;
    2. import org.camunda.bpm.extension.keycloak.plugin.KeycloakIdentityProviderPlugin;
    3. import org.springframework.boot.context.properties.ConfigurationProperties;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @ConfigurationProperties(prefix="plugin.identity.keycloak")
    7. public class KeycloakIdentityProvider extends KeycloakIdentityProviderPlugin {
    8. }

     在application.yml配置文件中添加以下配置:

    1. camunda.bpm:
    2. authorization:
    3. enabled: true
    4. plugin.identity.keycloak:
    5. keycloakIssuerUrl: http://localhost:9090/realms/camunda
    6. keycloakAdminUrl: http://localhost:9090/admin/realms/camunda
    7. clientId: camunda-identity-service
    8. clientSecret: XXXXXXXXXXXXXXXX
    9. useEmailAsCamundaUserId: false
    10. useUsernameAsCamundaUserId: true
    11. useGroupPathAsCamundaGroupId: true
    12. administratorGroupName: camunda-admin
    13. disableSSLCertificateValidation: true

    设置Springboot Security

    需要在pom.xml里面添加以下依赖:

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-securityartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-oauth2-clientartifactId>
    8. dependency>

    需要新增一个KeycloakAuthenticationProvider来连接Spring security和Camunda。在src里面新建一个目录sso,里面新建一个KeycloakAuthenticationProvider.java文件,内容如下:

    1. package com.roy.camunda.sso;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. import javax.servlet.http.HttpServletRequest;
    5. import org.camunda.bpm.engine.ProcessEngine;
    6. import org.camunda.bpm.engine.rest.security.auth.AuthenticationResult;
    7. import org.camunda.bpm.engine.rest.security.auth.impl.ContainerBasedAuthenticationProvider;
    8. import org.springframework.security.core.Authentication;
    9. import org.springframework.security.core.context.SecurityContextHolder;
    10. import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    11. import org.springframework.security.oauth2.core.oidc.user.OidcUser;
    12. import org.springframework.util.StringUtils;
    13. /**
    14. * OAuth2 Authentication Provider for usage with Keycloak and KeycloakIdentityProviderPlugin.
    15. */
    16. public class KeycloakAuthenticationProvider extends ContainerBasedAuthenticationProvider {
    17. @Override
    18. public AuthenticationResult extractAuthenticatedUser(HttpServletRequest request, ProcessEngine engine) {
    19. // Extract user-name-attribute of the OAuth2 token
    20. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    21. if (!(authentication instanceof OAuth2AuthenticationToken) || !(authentication.getPrincipal() instanceof OidcUser)) {
    22. return AuthenticationResult.unsuccessful();
    23. }
    24. String userId = ((OidcUser)authentication.getPrincipal()).getName();
    25. if (!StringUtils.hasLength(userId)) {
    26. return AuthenticationResult.unsuccessful();
    27. }
    28. // Authentication successful
    29. AuthenticationResult authenticationResult = new AuthenticationResult(userId, true);
    30. authenticationResult.setGroups(getUserGroups(userId, engine));
    31. return authenticationResult;
    32. }
    33. private List<String> getUserGroups(String userId, ProcessEngine engine){
    34. List<String> groupIds = new ArrayList<>();
    35. // query groups using KeycloakIdentityProvider plugin
    36. engine.getIdentityService().createGroupQuery().groupMember(userId).list()
    37. .forEach( g -> groupIds.add(g.getId()));
    38. return groupIds;
    39. }
    40. }

    保护REST API

    我们使用JWT来保护rest API的调用,如以下流程:

    1. 客户端在调用camunda rest API之前需要先从keycloak获取JWT
    2. 客户端在调用API的header里面设置JWT
    3. Camunda验证Token并从token中获取用户id和group id

    在application.yml里面增加以下配置:

    1. # Camunda Rest API
    2. rest.security:
    3. enabled: true
    4. provider: keycloak
    5. required-audience: camunda-rest-api

    为了能让keycloak在生成的JWT里面包括Camunda期望的audience claim,需要配置一个Client Scope,名称为camunda-rest-api,如下图:

     然后再mappers里面新增一个mapper,类型为Audience,然后配置需要的audience camunda-rest-api,如下图:

    最后把这个client scope加到之前创建的client Camunda-Identity-Service中,如下图:

    以上的配置可以使得经过Camunda-Identity-Service验证的用户能够访问Camunda的rest API

    在src目录下新建一个目录rest,然后新建一个文件RestApiSecurityConfig.java,内容如下,其中configure和jwtDecoder这两个函数中被注释的部分是原代码,我测试了发现无法检验token里面的aud以及对用户的role进行校验,因此需要改造一下。另外还新增了一个函数对Keycloak的token中的Role进行转换,即增加ROLE_的前缀。这个设置可以确保只有用户具有admin的role,并且client的audience是camunda-rest-api的才有权限访问engine-rest/的API:

    1. package com.roy.camunda.rest;
    2. import javax.inject.Inject;
    3. import org.camunda.bpm.engine.IdentityService;
    4. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    5. import org.springframework.boot.autoconfigure.security.SecurityProperties;
    6. import org.springframework.boot.web.servlet.FilterRegistrationBean;
    7. import org.springframework.context.ApplicationContext;
    8. import org.springframework.context.annotation.Bean;
    9. import org.springframework.context.annotation.Configuration;
    10. import org.springframework.core.annotation.Order;
    11. import org.springframework.core.convert.converter.Converter;
    12. import org.springframework.security.authentication.AbstractAuthenticationToken;
    13. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    14. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    15. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    16. import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
    17. import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
    18. import org.springframework.security.oauth2.core.OAuth2TokenValidator;
    19. import org.springframework.security.oauth2.jwt.Jwt;
    20. import org.springframework.security.oauth2.jwt.JwtDecoder;
    21. import org.springframework.security.oauth2.jwt.JwtValidators;
    22. import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
    23. import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
    24. /**
    25. * Optional Security Configuration for Camunda REST Api.
    26. */
    27. @Configuration
    28. @EnableWebSecurity
    29. @Order(SecurityProperties.BASIC_AUTH_ORDER - 20)
    30. @ConditionalOnProperty(name = "rest.security.enabled", havingValue = "true", matchIfMissing = true)
    31. public class RestApiSecurityConfig extends WebSecurityConfigurerAdapter {
    32. /** Configuration for REST Api security. */
    33. @Inject
    34. private RestApiSecurityConfigurationProperties configProps;
    35. /** Access to Camunda's Identity Service. */
    36. @Inject
    37. private IdentityService identityService;
    38. /** Access to Spring Security OAuth2 client service. */
    39. @Inject
    40. private OAuth2AuthorizedClientService clientService;
    41. @Inject
    42. private ApplicationContext applicationContext;
    43. /**
    44. * {@inheritDoc}
    45. */
    46. @Override
    47. public void configure(final HttpSecurity http) throws Exception {
    48. /*
    49. String jwkSetUri = applicationContext.getEnvironment().getRequiredProperty(
    50. "spring.security.oauth2.client.provider." + configProps.getProvider() + ".jwk-set-uri");
    51. http
    52. .csrf().ignoringAntMatchers("/api/**", "/engine-rest/**")
    53. .and()
    54. .antMatcher("/engine-rest/**")
    55. .authorizeRequests()
    56. .anyRequest().authenticated()
    57. .and()
    58. .oauth2ResourceServer()
    59. .jwt().jwkSetUri(jwkSetUri)
    60. ;
    61. */
    62. http.authorizeRequests(authorizeRequests -> authorizeRequests
    63. .antMatchers("/engine-rest/**").hasRole("admin")
    64. .anyRequest().authenticated()).oauth2ResourceServer(
    65. oauth2ResourceServer -> oauth2ResourceServer.jwt(
    66. jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
    67. )
    68. );
    69. }
    70. private Converterextends AbstractAuthenticationToken> jwtAuthenticationConverter() {
    71. JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
    72. jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
    73. return jwtConverter;
    74. }
    75. /**
    76. * Create a JWT decoder with issuer and audience claim validation.
    77. * @return the JWT decoder
    78. */
    79. @Bean
    80. public JwtDecoder jwtDecoder() {
    81. /*
    82. String issuerUri = applicationContext.getEnvironment().getRequiredProperty(
    83. "spring.security.oauth2.client.provider." + configProps.getProvider() + ".issuer-uri");
    84. NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
    85. JwtDecoders.fromOidcIssuerLocation(issuerUri);
    86. OAuth2TokenValidator audienceValidator = new AudienceValidator(configProps.getRequiredAudience());
    87. OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
    88. OAuth2TokenValidator withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
    89. jwtDecoder.setJwtValidator(withAudience);
    90. */
    91. String jwkSetUri = applicationContext.getEnvironment().getRequiredProperty(
    92. "spring.security.oauth2.client.provider." + configProps.getProvider() + ".jwk-set-uri");
    93. String issuerUri = applicationContext.getEnvironment().getRequiredProperty(
    94. "spring.security.oauth2.client.provider." + configProps.getProvider() + ".issuer-uri");
    95. OAuth2TokenValidator audienceValidator = new AudienceValidator(configProps.getRequiredAudience());
    96. OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
    97. OAuth2TokenValidator withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
    98. NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
    99. jwtDecoder.setJwtValidator(withAudience);
    100. return jwtDecoder;
    101. }
    102. /**
    103. * Registers the REST Api Keycloak Authentication Filter.
    104. * @return filter registration
    105. */
    106. @SuppressWarnings({ "unchecked", "rawtypes" })
    107. @Bean
    108. public FilterRegistrationBean keycloakAuthenticationFilter(){
    109. FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
    110. filterRegistration.setFilter(new KeycloakAuthenticationFilter(identityService, clientService));
    111. filterRegistration.setOrder(102); // make sure the filter is registered after the Spring Security Filter Chain
    112. filterRegistration.addUrlPatterns("/engine-rest/*");
    113. return filterRegistration;
    114. }
    115. }

    新增一个文件KeycloakRealmRoleConverter.java,进行Keycloak role的转换,内容如下:

    1. package com.roy.camunda.rest;
    2. import java.util.Collection;
    3. import java.util.Map;
    4. import java.util.stream.Collectors;
    5. import java.util.List;
    6. import org.springframework.core.convert.converter.Converter;
    7. import org.springframework.security.core.GrantedAuthority;
    8. import org.springframework.security.core.authority.SimpleGrantedAuthority;
    9. import org.springframework.security.oauth2.jwt.Jwt;
    10. public class KeycloakRealmRoleConverter implements Converter> {
    11. @Override
    12. public Collection convert(Jwt jwt) {
    13. final Map realmAccess = (Map) jwt.getClaims().get("realm_access");
    14. return ((List)realmAccess.get("roles")).stream()
    15. .map(roleName -> "ROLE_" + roleName) // prefix to map to a Spring Security "role"
    16. .map(SimpleGrantedAuthority::new)
    17. .collect(Collectors.toList());
    18. }
    19. }

    新建一个文件RestApiSecurityConfigurationProperties.java, 内容如下:

    1. package com.roy.camunda.rest;
    2. import javax.validation.constraints.NotEmpty;
    3. import org.springframework.boot.context.properties.ConfigurationProperties;
    4. import org.springframework.stereotype.Component;
    5. import org.springframework.validation.annotation.Validated;
    6. /**
    7. * Complete Security Configuration Properties for Camunda REST Api.
    8. */
    9. @Component
    10. @ConfigurationProperties(prefix = "rest.security")
    11. @Validated
    12. public class RestApiSecurityConfigurationProperties {
    13. /**
    14. * rest.security.enabled:
    15. *
    16. * Rest Security is enabled by default. Switch off by setting this flag to {@code false}.
    17. */
    18. private Boolean enabled = true;
    19. /**
    20. * rest.security.provider:
    21. *
    22. * The name of the spring.security.oauth2.client.provider to use
    23. */
    24. @NotEmpty
    25. private String provider;
    26. /**
    27. * rest.security.required-audience:
    28. *
    29. * Required Audience.
    30. */
    31. @NotEmpty
    32. private String requiredAudience;
    33. // ------------------------------------------------------------------------
    34. /**
    35. * @return the requiredAudience
    36. */
    37. public String getRequiredAudience() {
    38. return requiredAudience;
    39. }
    40. /**
    41. * @param requiredAudience the requiredAudience to set
    42. */
    43. public void setRequiredAudience(String requiredAudience) {
    44. this.requiredAudience = requiredAudience;
    45. }
    46. /**
    47. * @return the enabled
    48. */
    49. public Boolean getEnabled() {
    50. return enabled;
    51. }
    52. /**
    53. * @param enabled the enabled to set
    54. */
    55. public void setEnabled(Boolean enabled) {
    56. this.enabled = enabled;
    57. }
    58. /**
    59. * @return the provider
    60. */
    61. public String getProvider() {
    62. return provider;
    63. }
    64. /**
    65. * @param provider the provider to set
    66. */
    67. public void setProvider(String provider) {
    68. this.provider = provider;
    69. }
    70. }

    新增一个文件AudienceValidator.java,校验JWT中是否包括所需的audience,内容如下:

    1. package com.roy.camunda.rest;
    2. import org.springframework.security.oauth2.core.OAuth2Error;
    3. import org.springframework.security.oauth2.core.OAuth2TokenValidator;
    4. import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
    5. import org.springframework.security.oauth2.jwt.Jwt;
    6. /**
    7. * Token validator for audience claims.
    8. */
    9. public class AudienceValidator implements OAuth2TokenValidator {
    10. /** The required audience. */
    11. private final String audience;
    12. /**
    13. * Creates a new audience validator
    14. * @param audience the required audience
    15. */
    16. public AudienceValidator(String audience) {
    17. this.audience = audience;
    18. }
    19. /**
    20. * {@inheritDoc}
    21. */
    22. @Override
    23. public OAuth2TokenValidatorResult validate(Jwt jwt) {
    24. if (jwt.getAudience().contains(audience)) {
    25. return OAuth2TokenValidatorResult.success();
    26. }
    27. return OAuth2TokenValidatorResult.failure(
    28. new OAuth2Error("invalid_token", "The required audience is missing", null));
    29. }
    30. }

    新增一个文件KeycloakAuthenticationFilter.java,其作用是注册一个过滤器到Spring security的过滤器链的末尾,把验证过的user id和group id发送给Camunda的IdentityService,内容如下:

    1. package com.roy.camunda.rest;
    2. import java.io.IOException;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. import javax.servlet.Filter;
    6. import javax.servlet.FilterChain;
    7. import javax.servlet.ServletException;
    8. import javax.servlet.ServletRequest;
    9. import javax.servlet.ServletResponse;
    10. import org.camunda.bpm.engine.IdentityService;
    11. import org.slf4j.Logger;
    12. import org.slf4j.LoggerFactory;
    13. import org.springframework.security.core.Authentication;
    14. import org.springframework.security.core.context.SecurityContextHolder;
    15. import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
    16. import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    17. import org.springframework.security.oauth2.core.oidc.user.OidcUser;
    18. import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
    19. import org.springframework.util.StringUtils;
    20. /**
    21. * Keycloak Authentication Filter - used for REST API Security.
    22. */
    23. public class KeycloakAuthenticationFilter implements Filter {
    24. /** This class' logger. */
    25. private static final Logger LOG = LoggerFactory.getLogger(KeycloakAuthenticationFilter.class);
    26. /** Access to Camunda's IdentityService. */
    27. private IdentityService identityService;
    28. /** Access to the OAuth2 client service. */
    29. OAuth2AuthorizedClientService clientService;
    30. /**
    31. * Creates a new KeycloakAuthenticationFilter.
    32. * @param identityService access to Camunda's IdentityService
    33. */
    34. public KeycloakAuthenticationFilter(IdentityService identityService, OAuth2AuthorizedClientService clientService) {
    35. this.identityService = identityService;
    36. this.clientService = clientService;
    37. }
    38. /**
    39. * {@inheritDoc}
    40. */
    41. @Override
    42. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    43. throws IOException, ServletException {
    44. // Extract user-name-attribute of the JWT / OAuth2 token
    45. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    46. String userId = null;
    47. if (authentication instanceof JwtAuthenticationToken) {
    48. userId = ((JwtAuthenticationToken)authentication).getName();
    49. } else if (authentication.getPrincipal() instanceof OidcUser) {
    50. userId = ((OidcUser)authentication.getPrincipal()).getName();
    51. } else {
    52. throw new ServletException("Invalid authentication request token");
    53. }
    54. if (StringUtils.isEmpty(userId)) {
    55. throw new ServletException("Unable to extract user-name-attribute from token");
    56. }
    57. LOG.debug("Extracted userId from bearer token: {}", userId);
    58. try {
    59. identityService.setAuthentication(userId, getUserGroups(userId));
    60. chain.doFilter(request, response);
    61. } finally {
    62. identityService.clearAuthentication();
    63. }
    64. }
    65. /**
    66. * Queries the groups of a given user.
    67. * @param userId the user's ID
    68. * @return list of groups the user belongs to
    69. */
    70. private List getUserGroups(String userId){
    71. List groupIds = new ArrayList<>();
    72. // query groups using KeycloakIdentityProvider plugin
    73. identityService.createGroupQuery().groupMember(userId).list()
    74. .forEach( g -> groupIds.add(g.getId()));
    75. return groupIds;
    76. }
    77. }

    测试

    现在我们可以在这个Springboot项目中输入mvn clean install,以及mvn spring-boot:run来运行Camunda引擎了。

    在Keycloak中创建一个新的用户并加入到之前创建的camunda-admin组里面,新建一个client,例如名称为test-client,在Setting里面的Implicit Flow Enabled设置为ON,client scopes里面把camunda-rest-api赋予到assigned default client scopes。

    在Keycloak里面创建一个名为admin的role,并把这个role赋予给camunda-admin。

    之后我们在浏览器访问以下URL:http://localhost:9090/realms/camunda/protocol/openid-connect/auth?response_type=token&client_id=test-client&redirect_uri=http://localhost:9090/login

    这时会去到Keycloak的页面,输入之前创建的用户名和密码,验证通过之后在返回的网页中,URL里面会包括了access token,拷贝这个token。

    然后我们测试调用camunda的API,例如我们调用create deployment的API,创建一个工作流的部署,http://localhost:8080/camunda/engine-rest/deployment/create,直接调用会返回401 unauthorized的错误。在headers里面加入Authorization: Bearer token之后再调用,这是返回了403的错误,消息是没有对Deployment这个resource进行create的Permission。这是我就纳闷了,按照正常设置,用户已经是在camunda-admin这个组里面了,而且在Camunda启动的时候也看到日志显示这个用户组已经获得了所有Resource的ALL Permission,为什么还会出现问题呢?网上搜索了很久也没找到答案,后来我在application.xml里面增加了两行配置,打印DEBUG的日志:

    1. logging.level.org.camunda.bpm.extension.keycloak: DEBUG
    2. logging.level.org.springframework.web.client.RestTemplate: DEBUG

    重新启动Camunda,按照之前的流程再次调用创建deployment的API,这次我从日志中看到了以下信息:

    1. 2022-10-02 16:38:41.381 DEBUG 14471 --- [nio-8080-exec-6] o.c.b.e.k.rest.KeycloakRestTemplate : HTTP GET http://localhost:9090/admin/realms/camunda/users?username=b9b42747-01df-4f8c-9520-847de7fa7893
    2. 2022-10-02 16:38:41.382 DEBUG 14471 --- [nio-8080-exec-6] o.c.b.e.k.rest.KeycloakRestTemplate : Accept=[text/plain, application/json, application/*+json, */*]
    3. 2022-10-02 16:38:41.408 DEBUG 14471 --- [nio-8080-exec-6] o.c.b.e.k.rest.KeycloakRestTemplate : Response 200 OK
    4. 2022-10-02 16:38:41.408 DEBUG 14471 --- [nio-8080-exec-6] o.c.b.e.k.rest.KeycloakRestTemplate : Reading to [java.lang.String] as "application/json"
    5. 2022-10-02 16:38:41.412 DEBUG 14471 --- [nio-8080-exec-6] org.camunda.bpm.extension.keycloak : KEYCLOAK-01050 Keycloak group query results: []

    感觉camunda调用keycloak的API来查这个用户的信息,但是查到的结果为空。我进keycloak看了一下,发现Camunda调用keycloak API的usename参数其实对应的是keycloak的用户ID,这样是查不到数据的,如果把这个参数改为对应的keycloak的用户名,则能查到数据。看来定位到问题了。再回到这个Camunda的keycloak插件的描述中,可以看到对于useUsernameAsCamundaUserId这个选项的描述是:

    Whether to use the Keycloak username attribute as Camunda's user ID. Default is false. In the default case the plugin will use the internal Keycloak ID as Camunda's user ID.

    按字面理解,我原来以为如果设置为true则表示用keycloak的用户名来作为Camunda的用户ID,但是我现在设置了true,Camunda还是用keycloak的用户ID来查询,和我的理解不一样。我把这个选项设置为false,再次测试,发现这次就可以了,没有再报403错误了。

    最后总结一下,按照本文的设置,如果需要设置用户访问Camunda的权限,那么需要以下步骤的设置:

    1. 在Keycloak里面配置用户,赋予用户对应的role,把用户加到camunda-admin的组里
    2. 用户调用Keycloak的API来获取token,这里需要用有camunda-rest-api client role的client来获取token

    如果需要更进一步细化用户的权限管理,可以参考Camunda文档中关于Authorization的描述,赋予用户更细的控制力度。

  • 相关阅读:
    asp.net core之依赖注入
    shutdown的各类参数及用法
    剑指offer——JZ32 从上往下打印二叉树 解题思路与具体代码【C++】
    使用 Redis BitMap 实现签到与查询历史签到以及签到统计功能(SpringBoot环境)
    JS数组对象去重
    前端学习一、准备工作
    软件测试 - 基础(软件测试的生命周期、测试报告、bug的级别、与开发人员产生争执的调解方式)
    【Oracle】 instr函数与substr函数以及自制分割函数
    算法的时间复杂度和空间复杂度
    Wireshark TS | FIN 之后不关闭连接疑问
  • 原文地址:https://blog.csdn.net/gzroy/article/details/127088810