Skip to Content
Technical Articles

Step 8 with SAP Cloud SDK: Secure your Application on SAP Cloud Platform, Neo

The following steps will explain how secure applications in SAP Cloud Platform, Neo which are built based on the SAP Cloud SDK.

Note: This post is part of a series. For a complete overview visit the SAP Cloud SDK Overview.

Goal of this blog post

In the last tutorial you learned how to protect your application with authentication and authorization on SAP Cloud Platform, CloudFoundry. In this tutorial we will quickly show you what is required to cover the same aspects for a Neo-based application. As you will see, the Neo model on application security management is simpler compared to CloudFoundry. This stems from the fact, that on Neo the microservice-based approach is less established and requires therefore less configuration overhead. The tutorial covers the following aspects:

  1. Enhance your project with authentication and authorization metadata.
  2. Deploy the application with authentication information.

Prerequisites

Enable authentication

In fact, if you have used the archetype to generate your project for SAP Cloud Platform, Neo, the application is already enabled for authentication by default. To review the authentication go to your application/src/main/webapp/WEB-INF directory and look at the web.xml file.

<login-config>
    <auth-method>FORM</auth-method>
</login-config>

<session-config>
    <session-timeout>20</session-timeout>
</session-config>

<security-role>
    <role-name>Everyone</role-name>
</security-role>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>All SAP Cloud Platform users</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>Everyone</role-name>
    </auth-constraint>
</security-constraint>

There you can see that the archetype uses the FORM authentication method (which will use the SAML configuration). In addition you find the session configuration as well as the definition of one security role for Everyone. Per definition every authenticated user has the role “Everyone”. The entire model is based on the Java EE standard which is documented, for example, here.

Introduce a new role

Introducing a new role is fairly simple. You can add into the web.xml something like the following to introduce the BusinessPartnerManager role.

<security-role>
    <role-name>BusinessPartnerManager</role-name>
</security-role>

Now the paths of the application can be protected and constrained to certain roles you define for me. For a more fine-grained access-control, please refer to the standard JavaEE documentation.

Protecting resources with roles

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Only for business partner managers</web-resource-name>
        <url-pattern>/hello</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>BusinessPartnerManager</role-name>
    </auth-constraint>
</security-constraint>

After you made all changes you are ready to deploy the application to Neo.

Deploying to SAP Cloud Platform, Neo

cd application
mvn clean install

You can use the cockpit on SAP Cloud Platform to deploy or update your application as follows. Alternatively, use the deploy and start commands for Neo introduced in step 2 of this tutorial series.

Go to your SCP account on hanatrial.ondemand.com and select Applications / Java Applications / Deploy Application. Select the recent build from your application/target directory:

Hit “Deploy” and wait for the assembly to be uploaded. Once uploaded click the start button so that the application is going to be started.

While the application is starting, select the application and go to the Security / Roles menu.

Click the Assign button and assign your user ID to the BusinessPartnerManager role.

Afterwards, go back to the application and select the URL to try it out yourself:

Local deployment

In order to assign roles to users of the local deployment you have to do the following.

First go in your project and open the neousers.json file, e.g., using:

cd application/localServerContent/config_master/com.sap.security.um.provider.neo.local/
open neousers.json

Then add the roles to the respective users in the corresponding attribute:

"Roles": [ "BusinessPartnerManager" ]

After that, redeploy the application using

mvn scp:clean scp:push -pl application/

If you then go to http://localhost:8080/hello you should see the application in action behind a protected resource.

Small tips and tricks

You may want to consider the following additional attributes in your web.xml to decrease the likelihood of XSS and similar attacks (e.g., stolen JSESSIONID).

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Only for business partner managers</web-resource-name>
        <url-pattern>/hello</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>BusinessPartnerManager</role-name>
    </auth-constraint>
 	<user-data-constraint>
         <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<session-config>
	<session-timeout>20</session-timeout>
	<cookie-config>
		<secure>true</secure>
	</cookie-config>
</session-config>

That’s it for today. Now you have learned the basics to protect your application on SAP Cloud Platform, Neo which are based on the SAP S/4HANA Cloud SDK. Stay tuned for upcoming blog posts about more advanced usages of the SAP Cloud SDK.

12 Comments
You must be Logged on to comment or reply to a post.
  • Hello experts,

    I am following this tutorial set, but I am facing a problem while posting a new entity. I am working with Work Centers: https://api.sap.com/api/API_WORK_CENTERS/resource

    My class which is used to post a new capacity interval:

    @WebServlet(“/intervals”)
    public class IntervalServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final Logger logger = CloudLoggerFactory.getLogger(IntervalServlet.class);

    @Override
    protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
    throws ServletException, IOException {
    WorkCenterCapacityInterval interval = new WorkCenterCapacityInterval();
    //Create a helper class Converter to do data type conversions
    Converter helper = new Converter();
    try {
    //create Work Center Capacity interval

    interval.setIntervalStartDate(helper.stringDateToLocalDateTime(“2017-12-01 10:00:00”));
    interval.setIntervalEndDate(helper.stringDateToLocalDateTime(“2017-12-27 10:00:00”));
    interval.setCapacityActiveVersion(“1”);
    interval.setWorkCenterInternalID(“10000000”);
    interval.setWorkCenterTypeCode(“A”);
    interval.setCapacityInternalID(“10000000”);
    interval.setCapacityCategoryAllocation(“512”);
    interval.setAvailableCapacityIntervalDurn(“1”);
    interval.setWorkDayRule(“”);
    interval.setStdAvailableCapacityIsValid(true);

    logger.info(“Creating interval”);

    new DefaultWorkCenterDataService().createWorkCenterCapacityInterval(interval).execute();

    response.setStatus(HttpServletResponse.SC_CREATED);
    response.setContentType(“application/json”);
    response.getWriter().write(new Gson().toJson(interval));

    } catch (final Exception e) {
    logger.error(“Error while creating interval in SAP S/4HANA.”, e);
    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
    }

    }

    As a response I am getting “http status 405 – method not allowed”. I thought this part in web.xml handles token issues:

    <filter>
        <filter-name>RestCsrfPreventionFilter</filter-name>
        <filter-class>org.apache.catalina.filters.RestCsrfPreventionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>RestCsrfPreventionFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter>
        <filter-name>HttpSecurityHeadersFilter</filter-name>
        <filter-class>com.sap.cloud.sdk.cloudplatform.security.servlet.HttpSecurityHeadersFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HttpSecurityHeadersFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter>
        <filter-name>HttpCachingHeaderFilter</filter-name>
        <filter-class>com.sap.cloud.sdk.cloudplatform.security.servlet.HttpCachingHeaderFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HttpCachingHeaderFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    I checked the same POST command on Postman, and it posts the interval successfully.
    I would really appreciate any help.
    
    Best regards,
    Sona
    • Hello Sona,

      please answer the following questions:

      1. How do you access your servlet if not via Postman so that you get this error?
      2. Do you get the error as response of your endpoint, or is this error coming from SAP S/4HANA and caught as an exception by your code.
      3. When you say it works with Postman, I assume you mean directly calling the SAP S/4HANA system, correct?

      Best regards,

      Henning

      • Hello Henning,

        1. I deployed my app to Neo Cloud Platform. and I am accessing the servlet via appending
          /intervals to the the  the link generated by Neo once you start the app.
        2. The error message is caught in the catch block of the code. I also tried to deploy the app locally and got the following message:

          HTTP Status 405 – Method Not Allowed


          Type Status Report

          Message HTTP method GET is not supported by this URL

          Description The method received in the request-line is known by the origin server but not supported by the target resource.

        3. Exactly, I am using my communication user data as Authorization, give the body as xml file, fetch the token first with Get then pass it while posting.

        Thanks,

        Sona

        • Hello Sona,

          as far as I understand, you are trying to access the servlet via your browser by entering the URL there. Your browser will issue a GET request, which your servlet has not implemented. Hence the error message. This is independent of CSRF token handling or similar.

          You should be able to issue a POST request against your servlet using for example Postman. In that case, you likely need to provide a CSRF token that you fetched beforehand via an request with header X-CSRF-Token: Fetch.

          Best regards,

          Henning

  • Hello Henning,

    I implemented doGet. Now I am getting:

    HTTP Status 403 – Forbidden

    Here is the network result for POST:

    Request URL: https://---------.int.sap.hana.ondemand.com/firstapp-application/postinterval
    Request Method: POST
    Status Code: 403 
    Remote Address: ------------
    Referrer Policy: no-referrer-when-downgrade
    Content-Language: en
    Content-Length: 796
    Content-Type: text/html;charset=utf-8
    Date: Wed, 31 Jul 2019 15:26:47 GMT
    Server: SAP
    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
    X-CSRF-Token: Required
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding: gzip, deflate, br
    Accept-Language: en-US,en;q=0.9,de;q=0.8,pt;q=0.7
    Cache-Control: max-age=0
    Connection: keep-alive
    Content-Length: 214
    Content-Type: application/x-www-form-urlencoded
    Cookie: JSESSIONID=A595EBC6BDF8E5C64B0CEA8F1D9F989A05D0B6C1F3ABB18465C4D71EAEDF8D23; JTENANTSESSIONID_services=VkSfIc8SISML2ahn9o6Kj8V%2FPcWtu9WFAMEHtqa0tPE%3D; JTENANTSESSIONID_--------=bR1bl1ad24TDBjWx%2B%2Fd%2BqfkkyGHNcPSvbRW0XcXa8k4%3D; BIGipServer----------.int.sap.hana.ondemand.com=!82EbDC8bTS4Y/byfm8LAq5k/fj5zpR3zIwtSgf51Hyq+tHt9jxG0LxlZhiwhhrAk6Cp0lTCpczAERQ==
    Host: -------------.int.sap.hana.ondemand.com
    Origin: https://--------.int.sap.hana.ondemand.com
    Referer: https://-------.int.sap.hana.ondemand.com/firstapp-application/postinterval
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
    startdate: 2019-07-01T10:00
    enddate: 2019-01-02T10:00
    activeversion: 1
    capacityinternalid: 10000000
    workcenterinternalid: 10000000
    workcentertypecode: A
    capacitycategoryallocation: 512
    stdavailablecapacityisvalid: true

    (GET is successful)

    Best,

    Sona

    • Hello Sona,

      this is due to the missing CSRF Token – see the header X-CSRF-Token: Required.

      You first need to fetch a CSRF Token with a request that has the header X-CSRF-Token: Fetch. Afterwards, supply the returned token in the same header when doing the POST request.

      Best regards,

      Henning

      • Hello Henning,

        I edited my code so that in doGet I have:

        resp.addHeader("X-CSRF-Token", "Fetch");
        And in doPost I have:
        resp.addHeader("X-CSRF-Token", req.getHeader("X-CSRF-Token"));
        As a response I get:
        {
        "error": {
        "code": "0050569259751EE4BA9710043F8A5115",
        "message": {
        "lang": "en",
        "value": "In the context of Data Services an unknown internal server error occurred"
        },
        "innererror": {
        "transactionid": "D2EACF0F9EE40240E005D3CD79189FF2",
        "timestamp": "20190806074141.5733250",
        "Error_Resolution": {
        "SAP_Transaction": "For backend administrators: use ADT feed reader \"SAP Gateway Error Log\" or run transaction /IWFND/ERROR_LOG on SAP Gateway hub system and search for entries with the timestamp above for more details",
        "SAP_Note": "See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)"
        }}}}

         

        But when I reload the page I get the result I want:

        {
        "WorkCenterInternalID": "10000000",
        "WorkCenterTypeCode": "A",
        "CapacityCategoryAllocation": "512",
        "CapacityInternalID": "10000000",
        "CapacityActiveVersion": "1",
        "IntervalEndDate": 940982400000,
        "IntervalStartDate": 936144000000,
        "WorkCenter": "ASSEMBLY",
        "Plant": "1010",
        "WorkDayRule": "",
        "AvailableCapacityIntervalDurn": "7",
        "ShiftSequence": "",
        "StdAvailableCapacityIsValid": false
        }

        Thank you for your support.

        Best;

        Sona

        • Can you please explain where exactly you are sending and receiving those headers? I am surprised that you are handling those yourself in your Java code – normally, you only need to make sure that you follow the CSRF “protocol” from the client that you are using to call your servlet.

          The error you mention comes from the backend system – please explain if anything changed in how you call the backend using the SDK.

          Perhaps it is easiest if you paste the complete source code of your servlet.

  • Hi Henning,

    After adding RestCSRF filter to my web.xml, I can post a new entity or patch an existing one, if I am giving the parameters as URL query string, but when I try to post or patch giving the parameters as a json body, for example via postman, I am still getting  the error message that the csrf token is required. Of course I can give it manually on postman, but I wanted the application to handle it.

        <filter>
            <filter-name>RestCSRF</filter-name>
            <filter-class>org.apache.catalina.filters.RestCsrfPreventionFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>RestCSRF</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    There is no need now to add headers manually, because RestCSRF filter is supposed to handle it for me.

    Best,

    Sona

    • Hello Sona,

      When you write “I want the application to handle it”, do you refer to the Java application that you are building and which you have protected with the CSRF filter? This is not how it is supposed to work.

      CSRF prevention needs to happen in conjunction with the client, it is not something that can be handled in the backend alone. That is, the CSRF token has to be handled by the client, which could be a web page or a REST client. As you are writing that it works when manually doing this with Postman, I suspect that everything is working as expected.

      Here is some general documentation on the background of CSRF: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html

      Best regards,

      Henning