Search

    Security

    Describes authentication and authorization in CAP Java

    Content

    Overview

    With respect to web services, authentication is the act of proving the validity of user claims passed with the request. This typically comprises verifying the user’s identity, tenant, and additional claims like granted roles. Briefly, authentication controls who is using the service. In contrast, authorization makes sure that the user has the required privileges to access the requested resources. Hence, authorization is about controlling which resources the user is allowed to handle.

    Hence both, authentication and authorization, are essential for application security:

    ❗ Warning By default, CDS services are exposed to public. Proper configuration of authentication and authorization is required to secure your CAP application.

    Authentication

    User requests with invalid authentication need to be rejected as soon as possible in order to limit the resource impact to a minimum level. Ideally, authentication is one of the first steps when processing a request. This is one reason why it’s not integral part of the CAP runtime and needs to be configured on application framework level. In addition, CAP Java is based on a modular architecture and thus allows flexible configuration of the authentication method. Currently, XSUAA-authentication is supported out of the box, but a custom authentication can be configured as well. For the local development and test scenario, there’s a built-in mock user support.

    XSUAA Authentication with Spring Boot

    Your application is secured by XSUAA-authentication automatically, if

    1. Dependencies to following libraries are set:
      • XSUAA library for Spring
      • cds-feature-xsuaa
      • cds-feature-cloudfoundry
    2. The application is bound to an UAA service instance

    It’s recommended to use cds-starter-cloudfoundry bundle in the maven configuration, which covers all required dependencies for XSUAA-authentication.

    <dependency>
    	<groupId>com.sap.cds</groupId>
    	<artifactId>cds-starter-cloudfoundry</artifactId>
    </dependency>
    

    The individual dependencies can be explicitly added in the pom.xml file of your service, alternatively:

    <dependency>
    	<groupId>com.sap.cloud.security.xsuaa</groupId>
    	<artifactId>xsuaa-spring-boot-starter</artifactId>
    	<version>${xsuaa.version}</version>
    </dependency>
    <dependency>
    	<groupId>com.sap.cds</groupId>
    	<artifactId>cds-feature-xsuaa</artifactId>
    </dependency>
    <dependency>
    	<groupId>com.sap.cds</groupId>
    	<artifactId>cds-feature-cloudfoundry</artifactId>
    </dependency>
    

    Only if both, the library dependencies and a UAA service binding are in place, the CAP Java SDK activates a Spring security configuration, which enforces XSUAA authentication for all endpoints automatically:

    • Protocol adapter endpoints (managed by CAP such as OData V4/V2 or custom protocol adapters)
    • Remaining custom endpoints (not managed by CAP such as custom REST controllers or Spring Actuators)

    Here’s an example of a CDS model and the corresponding authentication configuration:

    service BooksService {
      entity Orders @(requires: 'Viewer') {
        [...]
      }
      @readonly entity Books {
        [...]
      }
    }
    
    Path Authenticated ?
    /BooksService x
    /BooksService/$metadata x
    /BooksService/Orders
    /BooksService/Books x

    Only the path the entity Orders is authenticated, as the model requires role Viewer.

    For multitenant applications, it’s required to authenticate all endpoints as the tenant information is essential for processing the request. In the current version, this isn’t configured automatically.

    There are several configuration parameters you can set in your application.yml, to control the behaviour of the auto-configuration:

    Configuration Property Description Default
    cds.security.authenticateUnknownEndpoints Determines, if authentication of custom endpoints is enforced. true
    cds.security.openUnrestrictedEndpoints Determines, if CAP endpoints that don’t require authorization also don’t enforce authentication. true
    cds.security.openMetadataEndpoints Determines, if OData $metadata endpoints are authenticated. false
    cds.security.xsuaa.enabled Switches off automatic XSUAA security configuration. true

    Customizing Spring Boot Security Configuration

    If you want to explicitly change the automatic security configuration, you can add an additional Spring security configuration on top that overrides the default configuration by CAP. This can be useful, for instance, if an alternative authentication method is required for specific endpoints of your application.

    @Configuration
    @EnableWebSecurity
    @Order(1) // needs to have higher priority than CAP security config
    public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http
        .requestMatchers().antMatchers("/public/**").and()
          .csrf().disable() // don't insist on csrf tokens in put, post etc.
        .authorizeRequests()
          .anyRequest().permitAll();
      }
    }
    

    Due to the custom configuration, all URLs matching /public/** are opened for public access.

    ❗ Warning
    Be cautious with the configuration of the HttpSecurity instance in your custom configuration. Make sure that only the intended endpoints are affected.

    Example:

    http.authorizeRequests().antMatchers("/public/**").permitAll()
    

    Opens all endpoints of the application, which is hardly intended.

    Another typical example is the configuration of Spring Actuators. For example a custom configuration can apply basic authentication to actuator endpoints /actuator/**:

    @Configuration
    @EnableWebSecurity
    @Order(1)
    public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
          http
          .requestMatchers().antMatchers("/actuator/**").and()
              .httpBasic().and()
          .authorizeRequests().anyRequest().authenticated();
        }
        @Override
        public void configure(AuthenticationManagerBuilder auth)
          throws Exception {
          // -> configure basic authentication here with PasswordEncoder etc.
        }
    }
    

    Custom Authentication

    You’re free to configure any authentication method according to your needs. CAP isn’t bound to any specific authentication method or user representation such as introduced with XSUAA, it rather runs the requests based on a user abstraction. The CAP user of a request is represented by a UserInfo object that can be retrieved from the RequestContext as explained in Enforcement API & Custom Handlers.

    Hence, if you bring your own authentication, you’ve to transform the authenticated user and inject as UserInfo to the current request. This is done by means of UserInfoProvider interface that can be implemented as Spring bean:

    @Component
    @Order(1)  // overrides the default
    public class MyUserInfoProvider implements UserInfoProvider {
    	@Autowired
    	MyAuthenticationContext customContext;
    
    	@Override
    	public UserInfo get() {
    		return UserInfo.create()
    			.setName(customContext.getUserName())
    			.setTenant(customContext.getTenant())
    			.setRoles(customContext.getScopes());
    	}
    }
    

    Remark: The same interface can also be applied to modify the UserInfo object only, for example, the object derived from XSUAA tokens by default.

    Mock User Authentication with Spring Boot

    By default, the CAP Java SDK creates a security configuration, which accepts mock users for testing purposes. The mock user configuration only applies if:

    • The service runs without an XSUAA service binding (none-productive mode)
    • Mock users are defined in the active application configuration

    You can define mock users in a Spring profile, which may be only active during testing, as in the following example:

    ---
    spring:
      profiles: test
    cds:
      security:
        mock:
          users:
            - name: Viewer-User
              password: viewer-pass
              tenant: CrazyCars
              roles:
                - Viewer
              attributes:
                Country: [GER, FR]
              additional:
                email: myviewer@crazycars.com
    
            - name: Privileged-User
              password: privileged-pass
              privileged: true
    
            - name: System
              password: system-pass
              system-user: true
              tenant: CrazyCars
              roles:
                - mtcallback
    
    • Mock user with name Viewer-User is a typical business user with SaaS-tenant CrazyCars who has assigned role Viewer and user attribute Country ($user.Country evaluates to value list [GER, FR]). This user also has the additional attribute email, which can be retrieved with UserInfo.getAdditionalAttribute("email").
    • Privileged-User is a user running in privileged mode. Such a user is helpful in tests that bypasses all authorization handlers.
    • Technical user System can be used, for example, to simulate SaaS registry calls for tenant provisioning in a multitenancy scenario.

    Property cds.security.mock.enabled = false disables any mock user configuration.

    A setup for Spring MVC-based tests based on the given mock users and the CDS model from above could look like this:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @AutoConfigureMockMvc
    public class BookServiceOrdersTest {
    	String ORDERS_URL = "/odata/v4/BooksService/Orders";
    
    	@Autowired
    	private MockMvc mockMvc;
    
    	@Test
    	@WithMockUser(username = "Viewer-User")
    	public void testViewer() throws Exception {
    		mockMvc.perform(get(ORDERS_URL)).andExpect(status().isOk());
    	}
    	@Test
    	public void testUnauthorized() throws Exception {
    		mockMvc.perform(get(ORDERS_URL)).andExpect(status().isUnauthorized());
    	}
    }
    

    Authorization

    CAP JAva SDK provides a comprehensive authorization service. By defining authorization rules declaratively via annotations in your CDS model, the runtime enforces authorization of the requests in a generic manner. Two different levels of authorization can be distinguished:

    • Role-based authorization allows to restrict resource access depending on user roles.
    • Instance-based authorization allows to define user privileges even on entity instance level, that is, a user can be restricted to instances that fulfill a certain condition.

    It’s recommended to configure authorization declaratively in the CDS model. If necessary, custom implementations can be built on the Authorization API.

    A precise description of the general authorization capabilities in CAP can be found in the Authorization guide.

    Role-Based Authorization

    Use CDS annotation @requires to specify in the CDS model which role a user requires to access the annotated CDS resources such as services, entities, actions, and functions (see Restricting Roles with @requires). The generic authorization handler of the runtime rejects all requests with response code 403 that don’t match the accepted roles. More specific access control is provided by the @restrict annotation, which allows to combine roles with the allowed set of events. For instance, this helps to distinguish between users that may only read an entity from those who are allowed to edit. See section Control Access with @restrict to find details about the possibilities.

    Instance-Based Authorization

    Whereas role-based authorization applies to whole entities only, Instance-Based Authorization allows to add more specific conditions that apply on entity instance level and depend on the attributes that are assigned to the request user. A typical use case is to narrow down the set of visible entity instances depending on user properties (for example, CountryCode or Department). Instance-based authorization is also basis for domain-driven authorizations built on more complex model constraints.

    Current Limitations

    The CAP Java SDK translates the where-condition in the @restrict annotation to a predicate, which is appended to the CQN statement of the request. This applies only to READ,UPDATE, and DELETE events. In the current version, the following limitations apply:

    • For UPDATE and DELETE events no paths in the where-condition are supported.
    • Paths in where-conditions with to-many associations or compositions aren’t supported.
    • UPDATE and DELETE requests that address instances that aren’t covered by the condition (for example, which aren’t visible) aren’t rejected, but work on the limited set of instances as expected.

    CAP Java SDK supports User Attribute Values that can be referred by $user.<attribute-name> in the where-clause of the @restrict-annotation. Currently, only comparison predicates with user attribute values are supported (<,<=,=,=>,>). Note, that generally a user attribute represents an array of strings and not a single value. A given value list [code1, code2] for $user.code in predicate $user.code = Code evaluates to (code1 = Code) or (code2 = Code) in the resulting statement.

    In the current version, an empty or not existing attribute list indicates unrestricted access.

    Enforcement API & Custom Handlers

    The generic authorization handler performs authorization checks driven by the annotations in an early Before handler registered to all application services by default. You may override or add to the generic authorization logic by providing custom handlers. The most important piece of information is the UserInfo that reflects the authenticated user of the current request. You can retrieve it:

    a) from the EventContext:

      EventContext context;
      UserInfo user = context.getUserInfo();
    

    b) through dependency injection within a handler bean:

      @Autowired
      UserInfo user;
    

    The most helpful getters in UserInfo are listed in the following table:

    UserInfo method Description
    getName() Returns the unique (logon) name of the user as configured in the IdP. Referred by $user and $user.name.
    getTenant() Returns the tenant of the user.
    isSystemUser() Indicates whether the request has been initiated by a technical service. Refers to pseudo-role system-user.
    isAuthenticated() True if the current user has been authenticated. Refers to pseudo-role authenticated-user.
    isPrivileged() Returns true if the current user runs in privileged (that is, unrestricted) mode
    hasRole(String role) Checks if the current user has the given role.
    getRoles() Returns the roles of the current user
    getAttributeValues(String attribute) Returns the value list of the given user attribute. Referred by $user.<attribute>.

    It’s also possible to modify the UserInfo object for internal calls. See section Request Contexts for more details. For instance, you might want to run internal service calls in privileged mode that bypasses authorization checks:

    cdsRuntime.requestContext().privilegedUser().run(privilegedContext -> {
    	assert privilegedContext.getUserInfo().isPrivileged();
    	// [...] Service calls in this scope pass generic authorization handler
    });
    
    Show/Hide Beta Features