cancel
Showing results for 
Search instead for 
Did you mean: 

How do I access a Java servlet from a UI5 application in Cloud Foundry?

michael_smithe5
Participant
0 Kudos

I have a UI5 application in Neo that currently calls a Java servlet (named UserInfo.java) which uses a remote function call to return an encrypted user token. I have followed the steps in Invoke ABAP Function Modules in On-Premise ABAP Systems to re-create the Java servlet in Cloud Foundry and it appears to be running. When I try to run the UI5 application in Cloud Foundry, however, I receive a 404 (Not Found) error on the Java application. We are using the following jquery statement to call the servlet:

jQuery.sap.syncGet('/userinfo/UserInfo').data

Here is the entry for the servlet in the xs-app.json of the UI5 application:

xs-app-entry.jpg

Here is how the servlet entry looks in the neo-app.json of the original UI5 application running in Neo:

neo-app-entry.jpg

What am I missing?

UPDATE: It appears the only way to access a servlet from UI5 in CF is by creating a standalone app router in my project, so I let BAS generate a brand new multi-target application, added a standalone app router module and a UI5 module. By following some other blogs, I converted the java servlet to a maven project (I guess this was required??), created and bound its necessary services in the CF cockpit, made the necessary adjustments to the xs-app.json, the mta.yaml, and the destinations.json files in the multi-target app, and added a small Text control to the UI5 view (this UI5 app is completely bare-bones and not the original app, by the way) to display the results of calling the servlet. So, I got a 401 error on the servlet, looked through the log of my servlet and found the "Request could not be authenticated . . . jwt token with audience . . . is not issued" error, which led me to this blog - https://blogs.sap.com/2020/09/03/outdated-sap_jwt_trust_acl .

I have created a xs-security.json file in both the multi target app and the java servlet with the scope grant and so forth. Unfortunately, I am still receiving the same 401 error. Please provide guidance when you have a moment. Attached are a screenshot of the error from the log, the xs-security file from the multi target app (mtatest) and the xs-security file from the java servlet (userinfo).

401error.jpg

xs-security.json of mtatest:

<code>{
  "xsappname": "mtatest",
  "tenant-mode": "dedicated",
  "description": "Security profile of called application",
  "authorities":["$XSAPPNAME(application,userinfo).Display"],
  "scopes": [
    {
      "name": "uaa.user",
      "description": "UAA"
    }
  ],
  "role-templates": [
    {
      "name": "Token_Exchange",
      "description": "UAA",
      "scope-references": [
        "uaa.user"
      ]
    }
  ]
}

xs-security.json of userinfo:

<code>{
  "xsappname": "userinfo",
  "tenant-mode": "shared",
  "scopes": [
    {
      "name": "$XSAPPNAME.Display",
      "description": "display",
      "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, mtatest)"]
    }
  ],
  "role-templates": [
    {
      "name": "Viewer",
      "description": "Required to view things in your solution",
      "scope-references"     : [
        "$XSAPPNAME.Display"
      ]
    }
  ]
}

Accepted Solutions (0)

Answers (1)

Answers (1)

CarlosRoggan
Advisor
Advisor
0 Kudos

Hello

I don’t fully understand your scenario, but hopefully it isn’t really required. In terms of security, only xsuaa instances talk to each other (OAuth clients and OAuth authorization servers, more precisely). Fortunately, the error message is helpful and you’ve found the helpful blog 😉
To me it sounds like the mtatest is sending a JWT token to the userinfo.
The validation of userinfo looks into the token and reads the aud claim
There, it doesn’t find its own clientID
But userinfo is required to be mentioned as “receiver”, to be in aud
So it fails.
How to solve it?
With grant.
But you’ve already declared the grant.
So what is missing?
I have only one idea:
The granting mechanism that you have used: it works only for client-credentials.
I think that maybe your scenario is different: probably you have a user-token.
I mean, probably you have a human user who logs into your app via approuter
Approuter is bound to mtatest-xsuaa.
Am I right?
In this case, the token is a user token
You can see in your error message that the aud contains “openid”
This is used for login and assigned to users.
So you should try the grant mechanism for user-scenario.
Please find a detailed description here
For granting you need:

"granted-apps" : ["$XSAPPNAME(application, mtatest)"]<br>

And for accepting you need

"foreign-scope-references": ["$XSAPPNAME(application,userinfo).Display"]

In addition, I guess you need to assign the required scope to the user

This is done in a role-template

      "scope-references": [
        "uaa.user", 
        "$XSAPPNAME(application,userinfo).Display"
      ]

Until now you’ve done a really great job while migrating to CF and setting up all that required stuff…..! 😉
Cheers and good luck !
Carlos

michael_smithe5
Participant
0 Kudos

carlos.roggan , I made those changes, but I am still getting the same error. 😞 I previously created a role collection called "all" in the subaccount, but this help documentation - https://help.sap.com/viewer/cca91383641e40ffbe03bdc78f00f681/Cloud/en-US/e862ab70c8304b0589491c018b3... - did not specify whether or not to add roles to it. Does that mean the empty "all" collection has access to every role or to no roles?

CarlosRoggan
Advisor
Advisor

wow - the documentation explains how to create a role collection and how to assign the role collection to a user (in Trust)
And yes, ONE ESSENTIAL step is missing:
add the desired roles to the role collection!!!
An empty role collection is really empty.
There are always 3 steps described:
Role - RoleCollection - User
I mean, even a role collection which contains roles is useless - as long as it is not assigned to a user.
Only THEN, the user will have roles assigned to him.

And one more hint:

If you've done these 3 steps - and still get error:
The reason might be that the user-login is kept in the session.
You need to log in again, otherwise the changes don't take effect and the scopes are not in the token.
So logout or clean and restart browser, all that is necessary.

hope this helps!

CarlosRoggan
Advisor
Advisor
0 Kudos

one more info:
I assume you've already done, but just to be on the safe side
In your destination, you need to have the forward Token property set to true.
This is essential for security scenario and this is the trick how to call protected service from UI5.
The UI never gets hold of the token, it is completely handled by approuter, which is more safe

"forwardAuthToken": true
michael_smithe5
Participant
0 Kudos

carlos.roggan , assigning the roles to the role collection got me a little further, past the 401 error at least, so thank you; however, now I am getting the 403 Forbidden error and I don't see anything in the log that indicates why it is forbidden. By the way, I had already set forwardAuthToken to true on the destination. The only I can see in the log of userinfo is that I got a successful login, which I wasn't getting before. Any thoughts as to what could be causing the 403 error?

CarlosRoggan
Advisor
Advisor
0 Kudos

Hi, good to hear that you're one step further.
Usually, 401 means that user is not authenticated (JWT completely refused)
403 means one step further, users identity is authenticated, but he does not have the required role for accessing this resource, so he is not authorized.
I don't know how your "userinfo" app does the authorization check.
Is there a static configuration? Do you need to specify which scope needs to be checked?
Is there a default mechanism which you need to override to provide the expected required scope?
Maybe one approach for troubleshooting:
You could fully remove the security configuration of your userinfo app. Then check the incoming JWT token.
It can be introspected, e.g. here jwt.io then use "debugger" to decode the content and view if your required scope is contained.

Note:
If you copy and paste the scope name into your code or static config, you need to be aware how the scope looks at runtime:

$XSAPPNAME.Display 
won’t become
userinfo.Display

instead, it will be something like

userinfo!t12345.Display

So you need to dynamically check the scope.
Programmatically access the content of $XSAPPNAME and concatenate the full scope name:

const UAA_CREDENTIALS = xsenv.getServices({myXsuaa: {tag: 'xsuaa'}}).myXsuaa

then

const auth = req.authInfo        
if (!auth.checkScope(UAA_CREDENTIALS.xsappname + '.Display')) {
res.status(403).end('Authorization missing.')
michael_smithe5
Participant
0 Kudos

carlos.roggan , actually the userinfo application does not need to do any scope checking. I just added the scope to the xs-security.json because I thought that was required for granting access to the approuter. So, how do I grant the approuter complete access to userinfo without specifying scopes?

CarlosRoggan
Advisor
Advisor
0 Kudos

Hi Michael,
Defining a scope in the xs-security.json and granting it to the mtatest is necessary, because otherwise the userinfo is not added to the aud.
However, this does only define the scope. This doesn't ENFORCE the scope. This doesn't mean that you must check the scope.
What I don't know is: if you aren't doing the check manually, who is doing it???
What library are you using?

One more hint:
I'm not familiar with the java spring security check mechanism (which I assume you're using?), but there should be a way to change the log level to DEBUG, such that you get more detailed info about the error

I have one more question:
Do you really need 2 XSUAA instances at all?

michael_smithe5
Participant
0 Kudos

carlos.roggan, we are not checking authorization in the web at all - if a user has access to the cloud subaccount, they can click on any tile in the portal site in Neo (which will be Launchpad in CF), the app linked to the tile will then call the userinfo/UserInfo servlet which calls a function module on the Gateway to return the encrypted user id, then that encrypted user id is passed from the UI into each Gateway service call and restful API call where it is decrypted. Authorization checking is done in the ABAP layer based upon the decrypted id. The approach prevents the security vulnerability of exposing the unencrypted user id in the UI network calls.

Also, we are not using java spring. The servlet is very similar to the example in Invoke ABAP Function Modules in On-Premise ABAP Systems . I don't know whether we need 2 XSUAA instances or not?? We have other UI5 apps we eventually need to bring onto CF as well that also use the userinfo utility - does that mean userinfo will need its own XSUAA instance or would it just be included in the XSUAA instance of each UI5 app?

CarlosRoggan
Advisor
Advisor
0 Kudos

Hello,
thanks for describing your scenario more in detail and I agree: it makes sense to have a separate utility-app that can be reused. It was just a question, there should be no reason why it shouldn’t work.

Today, I’ve set up a little scenario, to reproduce your scenario – and for me it works, even the scope is sent….
I can send you this mini-project, so you can check and compare.

Can you try calling the approuter-route directly, not via webapp, but directly the route-URL?

One more info, just in case:
In approuter config, xs-app.json file, if you don’t specify the properties authenticationMethod and authenticationType, then the default is always "xsuaa".
If you don’t need authentication, you have to set it to "none".

CarlosRoggan
Advisor
Advisor
0 Kudos

And one more hint:

I’ve set the logging level of approuter, so I can see all the logs:

cf set-env <yourapprouter>  XS_APP_LOG_LEVEL debug

Then I let the session expire.
Then I test my app and in the browser I get “Unauthorized”, HTTP code is 401
In the CF log I see the error message and the stack trace of approuter internal:

"msg":"GET request to /route/userinfo/ completed with status 401 Authentication required"

And the stacktrace shows that approuter tries to get the JWT token from session:

at SessionStrategy.strategy.pass

and fails:

@sap/approuter/lib/middleware/login-middleware.js:41:15)

This is just to give you an example for troubleshooting the approuter.
I’ve seen the sample java servlet given in your link, and I agree with you, this doesn’t seem to reject your request.
So it means that approuter would be the responsible.
Some configuration issue?
So please try to investigate with log level

michael_smithe5
Participant
0 Kudos

carlos.roggan , I sent you an email requesting the mini-project - I am hopeful that will help clear up my confusion. Thank you so much for all your patience and help!