3

In short -- I have two Spring Boot apps with Keycloak authentication running behind Nginx, and I'd like to use App2's content into App1 via SSI. Also, single sign on is a requirement, so there should not be a need to log in to both apps separately.


My setup is as follows:

  • Two Spring Boot applications (App1 and App2), running at localhost ports 9000 and 9001 respectively, both serve HTML markup that can be included in SSI for composing a final page. Both the applications are secure and require a user to be logged in. App1 has its context root at /, App2 has it at /app2
  • Keycloak server for managing user auth using OIDC. Both the App1 and App2 are configured to communicate with the server using Spring Security Keycloak Adapter and are registered as a single client in Keycloak
  • I'm using Nginx as a reverse proxy that is user facing. SSI is enabled. The App1 and App2 are registered under two separate locations in the Nginx configuration. Please see the nginx.conf file below.

Usecase that I'm trying to achieve:

  • App1 serves an HTML page that is basically a "shell" application -- contains a layout and header, footer. In the content area, there is an SSI include virtual directive, which pulls in the content from App2.
  • So in the end, what I want is to see a page served by App1, with contents from App2.
  • Obviously, both the apps require login, but since I've Keycloak configured and both the apps use same KC realm and Client ID, logging into any one of them should suffice

What is happening:

  • As soon as I hit App1 I've not logged in yet, I see the shell application without any protected content from App1 or App2. This is well and good
  • As soon as I log in to App1, the JSESSIONID is set with the cookie at path /. However, no content from App2 is seen [which should've come from the SSI include]
  • When I go to a page served by App2, it recognized the user instantly and there's no need to login again, and all the protected content is served. App2 functions as normal. App2 also stores a JSESSIONID cookie, at path /app2.

What I've tried so far:

  • Tried Nginx cookie path rewriting to store both the cookies at path /. This results in the two cookies conflicting and resetting each other.
  • Tried registering the App1 and App2 as separate clients in Keycloak
  • What I know is, when the shell page served by App1 is read by the SSI processor in Nginx, it needs to make a request to App2 to fill the SSI content, but for that, it needs the proper cookies for App2 to validate the request. Tried several trial and error approaches for this.

Is there something wrong with the usecase I'm trying to achieve, or something I'm doing wrong? Spring/Java/Keycloak/Nginx beginner here, thanks in advance.


nginx.conf file:

worker_processes  1;
events {
    worker_connections  1024;
}

error_log logs/error.log warn;

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        ssi on;
        client_max_body_size 100M;
        port_in_redirect off;

        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-App-Proto  http;
        proxy_set_header X-Forwarded-App-Port  80;

        # For App1
        location / {
            proxy_pass http://localhost:9000;
        }

        # For App2
        location /app2/ {
            # Notice no trailing / - it means whole URL will be preserved
            # i.e., "/app2" will not be discarded from the URL sent to proxy
            # which is what we want as our spring app has context root at "/app2"

            proxy_pass http://localhost:9001;

            # proxy_cookie_path ~*^/.* /; -- This results in conflicts in the two JSESSIONID cookies
        }

        error_page   302 404 500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

application.yml files of App1 and App2:

server:
  servlet:
    contextPath: /app2 # this is just '/' for App1
  port: 9001 # this is 9000 for App1
  use-forward-headers: true
  tomcat:
    protocol-header: X-Forwarded-App-Proto
    port-header: X-Forwarded-App-Port


########## Keycloak configuration
keycloak:
  auth-server-url: http://localhost:8080/auth
  realm: AppRealm
  resource: AppClient
  public-client: true
  principal-attribute: preferred_username
security:
  disable-csrf: true
aditya_medhe
  • 362
  • 1
  • 19
  • Very well written question! I would like to help you out, but I totally miss how SSI works :( Have you looked at the keycloak and app2 logs? It seems that somehow app1 tries to include content from app2 but it doesn't have any permission. Does this "include" happen at server side? – Aritz May 07 '20 at 22:16
  • @XtremeBiker Thanks. App2 and Keycloak show no logs. Maybe I need to increase the logging level to see if anything shows up. Regarding SSI, yes it happens at server side, it's basically a small processor in Nginx that interprets special HTML tags and based on them, pulls content from the URL in that tag, and inserts it at the place of the tag, before sending the response to the client. Even though you're not well versed with SSI, by a glance of the use case I'm trying to achieve, is there anything outright wrong or not the ideal way of implementation – aditya_medhe May 08 '20 at 03:34
  • I'm seeing some differences comparing to the nginx configuration I have for [my hobby project](https://gitlab.com/xtremebiker/carpooling/-/blob/master/src/main/docker/production/auth/config/nginx/auth.quienconduce.com.conf). Check the forwarded-for and forwarded-proto headers, these might make the difference. Also enable and check all the logs you can from the three actors. – Aritz May 08 '20 at 06:31

0 Answers0