added /app/rest support for non-UI requests

This commit is contained in:
2025-05-19 09:44:23 -04:00
parent faba551a2d
commit 880da07a84
3 changed files with 155 additions and 11 deletions

View File

@@ -1,5 +1,6 @@
package com.inteligr8.activiti.auth.oauth;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
import org.apache.commons.lang3.StringUtils;
@@ -14,11 +15,17 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import com.activiti.domain.idm.Capabilities;
import com.activiti.security.ActivitiAppRequestHeaderService;
import com.activiti.security.ActivitiRestAuthorizationService;
import com.activiti.security.ProtectedPaths;
import com.activiti.security.identity.service.config.IdentityServiceEnabledCondition;
import com.inteligr8.activiti.auth.service.JwtAuthenticationProvider;
@@ -43,6 +50,12 @@ public class IdentityServiceConfigurationOverride {
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired
private ActivitiAppRequestHeaderService appRequestHeaderService;
@Autowired
private ActivitiRestAuthorizationService restAuthorizationService;
@Bean("inteligr8.clientRegistrationRepository")
@Primary
public ClientRegistrationRepository clientRegistrationRepository() {
@@ -73,11 +86,13 @@ public class IdentityServiceConfigurationOverride {
}
/**
* Slightly lower priority than the one provided OOTB. This
* allows for the bean injection of the JwtAuthenticationConverter.
* Slightly higher priority than the one provided OOTB. This allows for
* the bean injection of the `JwtAuthenticationConverter`.
*
* A lower priority means it is applied last. This means it replaces the
* JwtAuthenticationConverter provided by Alfresco OOTB.
* This is basically a copy of what is provided OOTB, but:
*
* - The ability to configure the `JwtAuthenticationConverter`.
* - Allow non-UI access to `/app/rest/*`
*
* @see com.activiti.security.identity.service.config.IdentityServiceConfigurationApi#identityServiceApiWebSecurity
*/
@@ -85,11 +100,44 @@ public class IdentityServiceConfigurationOverride {
@Order(-5)
public SecurityFilterChain identityServiceApiWebSecurity(HttpSecurity http) throws Exception {
http
.securityMatcher(antMatcher(ProtectedPaths.API_URL_PATH + "/**"))
.securityMatchers(matchers -> {
matchers.requestMatchers(
// same as OOTB
antMatcher(ProtectedPaths.API_URL_PATH + "/**"),
// want to also allow non-UI access to the the protected API
// we do this for anything with an `Authorization` header, as the UI uses session-based authorization
new AndRequestMatcher(new RequestHeaderRequestMatcher("Authorization"), antMatcher(ProtectedPaths.APP_URL_PATH + "/rest/**"))
);
})
.csrf(csrf -> {
csrf.disable();
})
.cors(withDefaults())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Stores no Session for API calls
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwtConfigurer -> {
// here is where we are injecting a Spring extendible `JwtAuthenticationConverter`.
jwtConfigurer.jwtAuthenticationConverter(this.jwtAuthenticationProvider.create());
})
)
.authorizeHttpRequests(request ->
request
// same as OOTB
.requestMatchers(antMatcher(ProtectedPaths.API_URL_PATH + "/enterprise/**"))
.access(this.appRequestHeaderService)
.requestMatchers(antMatcher(ProtectedPaths.API_URL_PATH + "/**"))
.access(this.restAuthorizationService)
// borrowed from OOTB /app/rest security
.requestMatchers(antMatcher(ProtectedPaths.APP_URL_PATH + "/rest/reporting/**"))
.hasAuthority(Capabilities.ACCESS_REPORTS)
.requestMatchers(
antMatcher(ProtectedPaths.API_URL_PATH + "/**"),
antMatcher(ProtectedPaths.APP_URL_PATH + "/rest/**")
)
.authenticated()
);
return http.build();

View File

@@ -101,14 +101,14 @@
"profile",
"roles",
"basic",
"email"
"email",
"microprofile-jwt"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access",
"microprofile-jwt"
"offline_access"
]
},
{
@@ -156,15 +156,76 @@
"profile",
"roles",
"basic",
"email"
"email",
"microprofile-jwt"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access",
"microprofile-jwt"
"offline_access"
]
},
{
"clientId": "cli",
"name": "Command Line Tools",
"description": "",
"rootUrl": "",
"adminUrl": "",
"baseUrl": "",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "eJa5W7bv4ohFbr7QRtaCk0eccRFoYM5x",
"redirectUris": [
"/*"
],
"webOrigins": [
"/*"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1747506410",
"backchannel.logout.session.required": "true",
"standard.token.exchange.enabled": "true",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"basic",
"email",
"microprofile-jwt"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
],
"users": [

View File

@@ -0,0 +1,35 @@
@keycloakRealm = my-app
@keycloakBaseUrl = http://localhost:8081
@oauthUrl = {{keycloakBaseUrl}}/realms/{{keycloakRealm}}
@keycloakTokenUrl = {{oauthUrl}}/protocol/openid-connect/token
@oauthClientId = cli
@oauthClientSecret = eJa5W7bv4ohFbr7QRtaCk0eccRFoYM5x
@apsBaseUrl = http://localhost:8080/activiti-app
### Token
# @name token
curl -LX POST {{keycloakTokenUrl}} \
-H 'Content-type: application/x-www-form-urlencoded' \
-d "grant_type=client_credentials" \
-d "client_id={{oauthClientId}}" \
-d "client_secret={{oauthClientSecret}}"
@accessToken = {{token.response.body.access_token}}
@auth = Bearer {{accessToken}}
### APS Version
# @name version
GET {{apsBaseUrl}}/api/enterprise/app-version
Authorization: Bearer {{accessToken}}
### APS Tenants
# @name tenants
GET {{apsBaseUrl}}/api/enterprise/admin/tenants
Authorization: Bearer {{accessToken}}
@tenantId = {{tenants.response.body.0.id}}
### APS Templates
# @name templates
GET {{apsBaseUrl}}/app/rest/document-templates?tenantId={{tenantId}}&start=0&size=10&sort=sort_by_name_asc
Authorization: Bearer {{accessToken}}