Skip to content

Commit 03bee1e

Browse files
authored
cap-ams architecture (#37)
* add architecture documentation and diagrams for `cap-ams` module
1 parent 7c176a7 commit 03bee1e

1 file changed

Lines changed: 122 additions & 8 deletions

File tree

docs/Libraries/java/cap-ams.md

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This `cap-ams` module integrates AMS with CAP Java applications.
44

5+
See the [CAP Integration](/CAP/Basics) documentation for the relationship between cds annotations and enforcement with authorization policies.
6+
57
## Installation
68

79
Use the Spring Boot starter module:
@@ -17,18 +19,130 @@ Use the Spring Boot starter module:
1719
For non-Spring-Boot CAP Java applications, please open a support ticket to discuss integration options. The `cap-ams` module does not require Spring Boot, but it is tough to provide a flexible starter with replaceable, configurable bootstrapping without Spring's dependency injection and configuration features.
1820
:::
1921

20-
## Auto-Configuration
22+
## Architecture
2123

22-
The starter automatically configures:
24+
### Auto-Configured Beans
2325

24-
- `AuthorizationManagementService` bean from SAP Identity Service binding
25-
- CAP authorization integration for automatic role computation and AMS filter annotations
26-
- Request-scoped `CdsAuthorizations` proxy for manual authorization checks of the current user
27-
- Readiness state integration (via `spring-boot-starter-ams-readiness`)
26+
The starter creates the following beans:
2827

29-
## CAP Authorization Integration
3028

31-
See the [CAP Integration](/CAP/Basics) documentation for the relationship between cds annotations and enforcement with authorization policies.
29+
```mermaid
30+
graph TD
31+
AMS[AuthorizationManagementService] -->|autowired| AP[AuthorizationsProvider]
32+
AP -->|autowired| Proxy[CdsAuthorizations Proxy]
33+
AP -->|autowired| UIP[AmsUserInfoProvider]
34+
Proxy -->|autowired| CSH[Custom Service Handlers]
35+
Proxy -->|autowired| AH[AmsAuthorizationHandler]
36+
Proxy -->|autowired| RS[AmsCdsRouteSecurity]
37+
UIP -->|adds roles| UI["#lt;#lt;cds bean#gt;#gt; UserInfo"]
38+
```
39+
40+
| Bean | Role |
41+
|------|------|
42+
| `AuthorizationManagementService` | Core AMS client created from the SAP Identity Service binding |
43+
| `AuthorizationsProvider<CdsAuthorizations>` | Builds and (weakly) caches user authorizations based on UserInfo and SecurityContext (**Customization of library typically happens via this interface**)|
44+
| `CdsAuthorizations` (Proxy) | Singleton for performing authorization checks in the context of the current user's authorizations |
45+
| `AmsUserInfoProvider` (Internal) | CAP `UserInfoProvider` implementation that enriches `UserInfo` with cds roles granted by policies |
46+
| `AmsAuthorizationHandler` (Internal) | CAP event handler that injects instance-based WHERE conditions into `cds restrictions` in case of policy conditions for the computed role(s) |
47+
| `AmsCdsRouteSecurity` | Provides route-level authorization filters outside CAP framework |
48+
49+
### CdsAuthorizations
50+
51+
The `CdsAuthorizations` **bean** is a *singleton JDK Dynamic Proxy* that implements the `CdsAuthorizations` **interface**. Every method invocation on the proxy resolves the current user's authorizations with the `AuthorizationsProvider` bean based on the thread-local `RequestContext` and the corresponding `UserInfo`. Additional information is obtained from the thread-local `SecurityContext` if necessary, e.g. whether an SCI or XSUAA token has been used for authentication. This proxy mechanism makes authorization checks against the `CdsAuthorizations` interface very convenient, even though internally one instance of `CdsAuthorizations` is created and cached per request context.
52+
53+
54+
::: warning
55+
Avoid using the `CdsAuthorizations` bean for custom authorization checks unless necessary. It is best practice to use a declarative approach with cds annotations to let the AMS plugin handle role computation and filter generation while CAP handles the actual authorization enforcement based on the result.
56+
:::
57+
58+
#### Interface
59+
60+
The `CdsAuthorizations` interface extends the generic `Authorizations` interface with additional utility methods specific for the [role-based](/CAP/Basics.html#role-policies) CAP integration.
61+
62+
It can be used in any Spring context, e.g. custom service handlers, to check for authorizations if the annotation-based approach via the cds model is not sufficient:
63+
64+
```java
65+
import com.sap.cloud.security.ams.cap.api.CdsAuthorizations;
66+
import com.sap.cloud.security.ams.api.Decision;
67+
import com.sap.cloud.security.ams.api.expression.AttributeName;
68+
69+
@Component
70+
public class BookServiceHandler implements EventHandler {
71+
72+
@Autowired
73+
private CdsAuthorizations cdsAuthorizations; // singleton proxy — resolves per request
74+
75+
// Showcases a custom service handler that manually checks role permissions for a single-entity access. The example may not showcase best practices, but it serves to illustrate how to use the CdsAuthorizations proxy in custom code if necessary.
76+
@On(event = CdsService.EVENT_READ, entity = Books_.CDS_NAME)
77+
public void onReadBook(CdsReadEventContext context) {
78+
// ... resolve book entity from context ...
79+
80+
// Check if the user may use the Editor role for this specific book
81+
Decision decision = cdsAuthorizations.computeRoleFilters(
82+
"Editor",
83+
Set.of(), // we expect no unresolved attributes in the result — our input grounds all attributes
84+
Map.of(
85+
AttributeName.of("country"), book.getCountry(),
86+
AttributeName.of("genre"), book.getGenre()
87+
)
88+
);
89+
90+
if (decision.isDenied()) {
91+
throw new ServiceException(ErrorStatuses.FORBIDDEN, "Access denied");
92+
}
93+
94+
// decision.isGranted() — user may use Editor role for this book
95+
// !decision.isDenied() && !decision.isGranted() — policy condition could not be evaluated to boolean with given input -> unexpected behavior that requires trouble-shooting
96+
}
97+
}
98+
```
99+
100+
#### Caching Implementation
101+
102+
- The proxy uses `RequestContext.getCurrent(cdsRuntime)` to obtain the current request context and corresponding `UserInfo`. Then, it retrieves the current user's authorizations from the `AuthorizationsProvider` bean, which computes the authorizations based on the `UserInfo` and `SecurityContext`.
103+
104+
- `SciAuthorizationsProvider`, the default implementation of `AuthorizationsProvider`, caches computed authorizations in a `WeakHashMap` keyed by the `RequestContext` lifecycle reference — so within a single request, authorizations are resolved only once.
105+
- The `AmsUserInfoProvider` runs earlier (during `RequestContext` construction) to add AMS roles to `UserInfo`. To prevent a recursive stack overflow, it does not compute user roles with the `CdsAuthorizations` proxy (which would request the `UserInfo` again - forming a cyclic dependency). Instead, it presents the previous `UserInfo` to the `AuthorizationsProvider`.
106+
107+
### AmsCdsRouteSecurity
108+
109+
`AmsCdsRouteSecurity` provides Spring Security `AuthorizationManager` instances for route-level role checks with Spring Security outside the CAP framework.
110+
111+
::: warning
112+
Avoid using the `AmsCdsRouteSecurity` bean for custom authorization checks unless necessary. It is recommended to use a declarative approach with cds annotations to enforce authorization of application logic. The bean is meant for use cases where this is not practical.
113+
:::
114+
115+
`AmsCdsRouteSecurity` operates on the `CdsAuthorizations` proxy and offers two semantics:
116+
117+
- **`checkRole(role)`** — grants access only if the role is unconditionally granted (no conditions). Rejects conditional and denied decisions.
118+
- **`precheckRole(role)`** — grants access if the role is not definitely denied (i.e., granted or conditional). Use this only when the service layer implements an additional contextual authorization check that enforces the filter conditions.
119+
120+
Multi-role variants `checkAnyRole`, `checkAllRoles`, `precheckAnyRole`, and `precheckAllRoles` combine multiple role checks with OR/AND semantics respectively.
121+
122+
```java
123+
@Configuration
124+
public class SecurityConfiguration {
125+
126+
@Bean
127+
public SecurityFilterChain filterChain(HttpSecurity http, AmsCdsRouteSecurity via) throws Exception {
128+
http.authorizeHttpRequests(authz -> authz
129+
.requestMatchers("/actuator/health").permitAll()
130+
// Custom REST endpoint outside CAP — requires unconditional Admin role
131+
.requestMatchers("/api/admin/**").access(via.checkRole("Admin"))
132+
.anyRequest().denyAll()
133+
)
134+
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
135+
136+
return http.build();
137+
}
138+
}
139+
```
140+
141+
::: tip
142+
The `.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))` call configures Spring Security to validate incoming Bearer tokens as JWTs. This is required for any application that receives OAuth2 tokens (from SAP Identity Service / IAS). Without it, Spring Security will not extract the authenticated principal from the `Authorization` header, and no `SecurityContext` will be available for downstream AMS checks.
143+
144+
CAP Java applications that use `cds-starter-cloudfoundry` or `cds-starter-k8s` typically get this configured automatically. You only need to declare it explicitly if you provide a custom `SecurityFilterChain` bean (which overrides the auto-configured one).
145+
:::
32146

33147
## Configuration Properties
34148

0 commit comments

Comments
 (0)