Loading src/main/java/com/inteligr8/activiti/auth/oauth/IdentityServiceConfigurationOverride.java +53 −5 Original line number Diff line number Diff line 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; Loading @@ -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; Loading @@ -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() { Loading Loading @@ -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 */ Loading @@ -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(); Loading src/test/resources/keycloak-import/realm-my-app.json +67 −6 Original line number Diff line number Diff line Loading @@ -101,14 +101,14 @@ "profile", "roles", "basic", "email" "email", "microprofile-jwt" ], "optionalClientScopes": [ "address", "phone", "organization", "offline_access", "microprofile-jwt" "offline_access" ] }, { Loading Loading @@ -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": [ Loading src/test/vscode/simple.http 0 → 100644 +35 −0 Original line number Diff line number Diff line @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}} Loading
src/main/java/com/inteligr8/activiti/auth/oauth/IdentityServiceConfigurationOverride.java +53 −5 Original line number Diff line number Diff line 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; Loading @@ -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; Loading @@ -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() { Loading Loading @@ -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 */ Loading @@ -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(); Loading
src/test/resources/keycloak-import/realm-my-app.json +67 −6 Original line number Diff line number Diff line Loading @@ -101,14 +101,14 @@ "profile", "roles", "basic", "email" "email", "microprofile-jwt" ], "optionalClientScopes": [ "address", "phone", "organization", "offline_access", "microprofile-jwt" "offline_access" ] }, { Loading Loading @@ -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": [ Loading
src/test/vscode/simple.http 0 → 100644 +35 −0 Original line number Diff line number Diff line @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}}