Technical Articles
Securing Cloud Platform Business Application
CAP Security
ATTENTION: This information on blog has been deprecated! For CAP Security please follow the instructions on: https://cap.cloud.sap/docs/java/security I’m keeping this blog on-line just for reference purposes. |
Pre-requisites
Follow the tutorial on how to create the SCP-Business Application (CAP)
The MTA project should have at least three main modules: db, srv & app.
Java run-time and Maven installed on your PC
Valid SAP Marketplace User with software download privileges
AppRouter
After you finished adding the html5 module as explained in the CAP documentation, there is no need to create a NodeJS module and make it load the AppRouter. If you inspect the html5 module it contains the package.json with all node dependencies and the start script to load the application router. This application will be responsible for users authentication in Cloud Foundry. From there you can “share” the JWT token to the service module.
At this point you should have a working application with an UI front-end such as this one:
This application will never request authentication to you, since it is not really deployed onto CF. Instead WebIDE runs it in test mode, by simply pointing the browser to a resource path from your workspace. The only application you should have on your org/space should be another test application which corresponds to your OData service – a Java Spring based application.
In order to test the authentication from your “embedded” AppRouter, you need to:
- Clean up your trial account (remove any running applications, routes or any other resources). WebIDE will throw a deployment error, but it will not really tell you what’s wrong. If this is the first time you’ve tried CAP or WebIDE full-stack and your CF space is empty, then just hit stop on your service and WebIDE will remove the temporary application instance.
- Build the whole project – which will generate an mtar file on your workspace folder named “mta_archives”. By right-clicking on it you can select deploy.
Deployment should take a a fairly long time – but you need to keep in mind that it is doing a lot of work behind the scenes for your. For instance: you no longer need to manually create an xsuaa instance with xs-security.json file, etc.
After deploying the application you should see the following apps and service instances:
Test the app module by opening its URL and see that it now requests user authentication:
CF authentication uses the e-mail address to authenticate. So, don’t use the P-User here. Once you open your application the OData service should be called without any issues.
However, the request for authentication here has just protected the front-end UI, not the service.
OData Service Security
As explained before the ‘srv’ module is a Java Spring application. Therefore we need to use the Spring Security configuration to block access when the OData service is being called outside of the the UI and have it accept the JWT from the UI as authentication method.
This can be accomplished in Spring by two means. Either you create a new configuration class with the security beans in place – Spring will take care of making it secure via dependency injection. Or you could configure it via xml artifacts. Here I’ve chosen the xml artifacts, since CAP left a “template” configuration that is almost ready to be used.
To enable Spring Security in our Java application do the following:
- Open the file “web.xml” that is under the path /srv/src/main/webapp/WEB-INF. The file already comes with the required security filters in place, but you have to enable it by un-commenting last part. Remember to remove only the double dashes and the exclamation mark from the listener and filter mapping tags.
- Update the xsd definitions in the file “security-spring.xml” (under the same folder) to reflect the newer Spring version used by CAP:
Add the following tags to remove CSRF protection (we wont need this protection since all communication will be done internally between the router app and the service app).
Place the following piece of xml right above the tag <sec:intercept-url….> used to intercept the odata/v2 path
<sec:csrf disabled="true"/>
The spring-security.xml file should now look like this:
Once this is done, the java application “coding” is ready intercept all calls to the odata/v2 path and will only allow it to pass when there is a JWT present.
However, the way it configured still won’t work because we haven’t instructed the application where to fetch the required libraries.
XS Java Libraries
WebIDE works with Maven Central by default. Thus, it fetches all required libraries from it. If any dependency is not available in Maven Central, WebIDE won’t be able to build the application.
In order to use the Filter Chain you configured via web.xml, your Java project needs to reference the the HANA 2.0 Java Libraries – more specifically the ones related to security. Since they aren’t available in Maven Central nor at any other known public maven repositories we need “trick” the maven process and make it “think” it has access to the required libraries via the project’s folder structure.
Java Libraries in your PC
We first need to install the libraries in your local maven repository. The following procedure will copy pre-compiled libraries in the expected folder structure under your “.m2” folder.
To do it, download the XS JAVA library from the SAP Marketplace or search it from the following path:
Unzip the XS_JAVA_2-<version>.ZIP to a folder of your choice. Open a terminal window or command prompt and run the following commands:
#]> cd XS_JAVA_2_<version>
#]> mvn clean install
The result should be something very much like this:
Create zip file to import
Now, this is something your need to do on your own. Create two sets of zip files containing the following files/folders (which you should retrieve from your .m2 directory):
NOTE: The paths are important here. So, use your zip utility software to store the paths (the first line where we can clearly see “\com\sap\xs2\secutiry\…” and “\com\sap\security”. Under the shown folders you should see another folder for the library version and then the JAR file, POM, etc will be found as well. I am not going to document this procedure but I am confident that you are able to do it on your own.
XS Java Libraries in WebIDE
On WebIDE, create a new folder under your MTA project named “libs”. At this point you should have the following folder structure:
Right-click the libs folder and import the libs.zip file you created earlier:
Do the same for the libs2.zip – this time the import screen will tell you that the folder isn’t empty. Hit “import” as it is not going to overwrite the imported files from the previous step.
At this point the folder structure should look like this in WebIDE:
Let’s take the java-container-security folder as an example of what you should have in terms of files.
Update you POM.xml file to use of XS Java Libraries
Under the OData Module (folder “srv”) you will find the file pom.xml. Open open a new line between the </properties> tag and the <build> tag:
Then paste the following lines
<repositories>
<repository>
<id>my-local-repo</id>
<url>file://${project.basedir}/../libs</url>
</repository>
</repositories>
<dependencies>
<!-- Authentication and Authorization imports with Spring Security -->
<dependency>
<groupId>com.sap.xs2.security</groupId>
<artifactId>security-commons</artifactId>
<version>0.28.6</version>
</dependency>
<dependency>
<groupId>com.sap.xs2.security</groupId>
<artifactId>java-container-security</artifactId> <!-- 1 -->
<version>0.28.6</version>
</dependency>
<dependency>
<groupId>com.sap.xs2.security</groupId>
<artifactId>java-container-security-api</artifactId>
<version>0.28.6</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.sap.security.nw.sso.linuxx86_64.opt</groupId>
<artifactId>sapjwt.linuxx86_64</artifactId>
<version>1.1.19</version>
</dependency>
</dependencies>
The file should look like this:
These lines will make the OData service module load the security libraries required for the JWT token and all other security libraries. Keep an eye on the “Problems View” in WebIDE. If your pom.xml is invalid there is a high change your ‘libs’ folder structure isn’t correctly set or you need to update the versions in the XML. This blog was written when version 0.28.6 was the latest available. But you could have downloaded a newer version by now. If you place the cursor over the red marker, it will show the libraries that the system didn’t find.
Checking the WAR file
Build the OData service project and check that the corresponding library jar files were in fact downloaded from your local ‘libs’ folder and stored in the generated war file. The best way to find out is to export the war file from the target folder:
When you open the downloaded WAR file you should see the following newly included ‘libs’ under WEB-INF\lib:
You could also check the the build console and search for these files instead of downloading the whole war file (which is quite huge and may take some time to download).
Checking the security in the OData service:
The security features of your srv module will not work by default as there is no xsuaa dependency for it yet. To make your OData service use the xsuaa service we need to add the following lines on our MTA descriptor (it is exactly the same as the one defined four our ui front-end):
- name: uaa_bookshop2
properties:
SAP_JWT_TRUST_ACL: '[{"clientid" : "*", "identityzone" : "*"}]'
Your MTA file should look like this:
Now, if you run the service module, you will get the following error:
This means your service is now protected.
Putting it all together
First we have to make the AppRouter call the OData service destination in an authenticated manner. Therefore, change the property authenticationType from none to xsuaa on the file xs-app.json (this file is at the root of your app’s folder):
Keep in mind that this file is only interpreted when it is running in CF. Remember that AppRouter is a NodeJS module (impossible to run it on Neo). If you try running this directly via WebIDE it is not going to share the authentication between apps, simply because WebIDE web application testing doesn’t deploy and run the AppRouter. It is intended to test the UI, not the security. Maybe the product team will change this in the future, but I don’t hold the answers to such questions.
In a trial instance you must stop the OData service and make sure your org/space is clean of deployed projects due to limited resources.
Build the MTA project again to include the all the changes in the mtar file and deploy it to your org/space.
Check that all applications are really running. Sometimes the db module refuses to start right after deployment has finished. If this happens, just start the db module manually.
Final testing
After deployment is done, you should end-up with the same three applications (db, srv, app):
And an instance of xsuaa service bound to app and srv. In addition to the instance of hanatrial bound to db and srv:
Open the app URL in incognito mode to test the authentication. The CF authentication kicks in and you will be able to load the books table. Notice that the OData service loaded without any errors:
To prove that the OData service will only work after it has been authenticated, you can open the OData end-point from the app application on a new incognito window. It should load the login screen instead.
However, if you try loading the srv URL directly on an incognito window, you should still see the following:
Roles, Scopes, Role Templates and Role Collections
CAP will create a Role Template named “Token_Exchange” with scope named “uaa.user”:
However, there is no “role’ and no “role collection” created. In fact, there is no need for them in this example. We are simply checking for authenticated users. The service isn’t blocking any of the scopes.
If you need a more granular control over the methods of the service then define additional authorization scopes in the AppRouter’s “xs-security.json”. Then perform checks in the odata service via “spring-security.xml” this way:
<sec:intercept-url pattern="/odata/v2/CatalogService/Books" access="#oauth2.hasScope('${xs.appname}.uaa.user')" method="GET" />
<sec:intercept-url pattern="/odata/v2/CatalogService/Books" access="#oauth2.hasScope('${xs.appname}.uaa.admin')" method="POST" />
Once you have the ‘app’ module deployed, the new scopes and templates will be available to create the required roles based on the templates you defined in the AppRouter.
Roles are assigned to Role Collections which in turn can be assigned to users. This is done via cockpit at the organization level (not the space). Create the Role Collection and assign Roles to it.
Then on the ‘Security’ open the ‘Trust’ menu and you will find your IdP link. Click on the link and assign a user to the Role Collection. Remember: users in CF are defined as e-mails.
Cheers!
Ivan Mirisola
Thank you so much, Ivan, for this awesome tutorial! Great explanation of a crucial topic for cloud development!
I finally get to the login screen after locking down my OData service. Currently I'm facing a weird issue that I can't login on the login page
https://authentication.eu10.hana.ondemand.com/login
with my SAP ID user account. The credentials work just fine in the Cloud Platform Cockpit but not there. I opened a ticket to SAP and I hope it gets resolved soon. But at least I have some hope now to get this all working 🙂
Cheers,
Dominik
Hi Dominik,
Are you using your SAP ID user id or the e-mail address to login? CF instances will accept e-mail as login id and not P-Users, S-Users.
If you are getting errors trying to login, maybe it would be a good idea trying to look into the http traces or the odata service logs to troubleshoot the issue.
Best regards,
Ivan
Hi Ivan,
I used the e-mail address of my S-User account. Those credentials work fine in the cloud cockpit, but not in the xsuaa login. I get the same error messages when I try to open the XSUAA dashboard - can't get past the login. It's the same for the users of my colleagues so I guess something with our company CP account is borked here...
I'll post if it gets resolved and I can hopefully get the OData service login working.
Kind regards,
Dominik
Heureka! I finally got it working!
I even got an AppRouter running within the Web IDE test environment, so you don't have to deploy the whole application every time and can use the built-in debugger.
I just need some time to gather my thoughts and material, then I'll post an update with a how to.
Thanks again, your detailed descriptions were spot on!
Thank you for sharing a wonderful blog.
It just fits my needs.
I have some questions:
I’m looking forward to your future blogs.
Cheers,
Tri
Hi Tri,
Best resources to look for such information is the HANA Developer Guide.
Best regards,
Ivan
Hi Ivan,
Thank you for your information. It’s very helpful.
Just one more question is regarding user info attribute, where can I find documents about it? I mean what attributes it contains.
I did have a look at your link to HANA developer guide but I can't find it anywhere.
Regards,
Tri
Hi Tri,
You define attributes in the "Application Security Descriptor" (file: xs-security.json)
The example below defines Country as being an attribute of type String and Cost Center as being of type Integer.
https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.03/en-US/3bfb120045694e21bfadb1344a693d1f.html
You can define its values via Cloud Cockpit on an application's security role. The cockpit fetches the security settings that were defined on the app's assigned xsuaa instance. Once you create a new Role based on a Role Template the system will allow you to assign a static value or a dynamic value (which is based on the user persistence store being used for SAML). Let's say you want to map users from an LDAP to the CC = 160 in your cloud application. You should use the dynamic setting for Cost Center and use the exposed Claim attribute "costCenter" of your SAML IdP.
Best regards,
Ivan
Hi Ivan,
I've followed your blog to protect my HANA XSA based OData Service. To get access to the SecurityContext / UserInfo I had to include also the maven https://github.com/SAP/cloud-security-xsuaa-integration / https://search.maven.org/search?q=com.sap.cloud.security into my project. Maybe you need to update your post.
Best regards
Gregor
Hi Gregor,
Thanks for the feedback. I really appreciate it.
I've went through the library and it seems to be a new library released by SAP. And it seems to be very simmilar to the xs2 library I used.
Do you mean that instead of using the xs2 libraries (worarround from my blog) you used the mentioned library and security worked?
Best regards,
Ivan
Very interesting,good job and thanks for sharing such a good post. Thanks for the step by step tutorial. awsome!
Thanks, I really appreciate it.
Nice blog, the article you have shared is good.This article is very useful. My friend suggest me to use this blog.
Thank you for every other excellent post. Where else could anyone get that type of info in such a perfect approach of writing? I’ve a presentation next week, and I’m at the search for such information.
Hi zainab khan,
Sorry for the late response and thanks. I really appreciate it. I didn't catch your comment earlier. So I suppose you have all you need now, correct?
Best regards,
Ivan
Hi Ivan, Very helpful post.
I have one questions. I am working on a project based on Distro in which I want to use the Cloud platform app. My question is What If I want to map users from an LDAP to the CC = 160 in my cloud application.
Is it necessary to use the dynamic setting for Cost Center and use the exposed Claim attribute “cost center” of your SAML IDP?
Hi Patric,
Thanks! I really appreciate it.
Regarding attribute mapping, you can set it up on your account according to the Trust configuration. Your LDAP server needs to be exposed a SAML service (accessible by SCP). Once this is done, you will have configure the SAML assertion token to contain the user property that defines a Cost Center. Once you have that property available on the assertion token, you can proceed with the definition of it as being an attribute reference. When you build the role, you will have to specify the cost center as being static or dynamic. If it is static, you will enter its value, if it is dynamic you will set it to the property from the SAML token.
https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/713f52ac36a041ef8fdc72560d6cfbcd.html?q=attribute%20references
The documentation is for SCP applications, but it works the same as in HANA 2.0 XSA.
Best regards,
Ivan
Thank you so much Ivan for this wonderful blog. It guided me step by step to achieve what i was looking for.
Just one quick question.
I have provided the user attribute - Cost Center
It comes in JWT under "xs.user.attributes"
We are using the CAP model. Here in the service i need to read this value. I tried $user = MyEntity.Email, it works. But when i try to read the "Cost Center" attribute and go COSTCENTER = $user.xs.user.attrubutes.CostCenter[0], it fails. Do you have any idea or resource links which could help me.
Hi Prodipto Lahiri,
Have you tried the following?
Best regards,
Ivan
Hi Ivan Mirisola,
Sometimes users with valid accounts start receiving 403 errors, and users who at the time had an active session continue to work quietly and the problem with 403 is solved by restarting the service. 403 happens at check user’s scopes how is this possible?
using com.sap.cloud.servicesdk.prov: 1.40.0
Hi Albert Zarankovich,
I really need to update this blog as some information is now unnecessary.
Have you seen CAP's security features already?
https://cap.cloud.sap/docs/java/security
Best regards,
Ivan