Wir wollen unsere Anwendung durch einen Filter schützen, der nur Anfragen von eingeloggten Benutzern mit der richtigen Rolle hindurch lässt.
Für diesen PoC bauen wir einen Reverse Proxy für den Login und einen Anwendungsserver für den Filter. Der Anwendungsserver bekommt einen Zugang über den RP und einen Zugang ohne RP, um so zu zeigen, dass nur über den RP auf die Anwendung zugegriffen werden kann.
In produktiven Umgebungen darf es den Zugang ohne RP so ungeschützt nicht geben, da es sehr einfach ist, zB über ein Browser-Plugin, den RP mittels selbstgesetzter Header zu faken.
Reverse Proxy
Der Reverse Proxy bekommt grundsätzlich die gleiche Konfiguration wie in den Beispielen zuvor.
Hier müssen wir aber noch den Scope params hinzufügen, um so die OneLogin-Rollen des Benutzers zu übertragen:
## OIDCScope params ## to put params including roles into header OIDCScope "openid email profile groups params"
Im Header sieht das dann beispielsweise so aus:
oidc_claim_params {"post_logout_redirect_uri": "http://localhost", "roles": "user;admin"}
ACHTUNG
Das die Rollen als Parameter übertragen werden, funktioniert nur, wenn man explizit angibt, dass diese als Parameter übergeben werden sollen!
Dazu muss die Application in OneLogin entsprechend konfiguriert werden:
Applications -> Applications -> select Application -> Parameters -> "+"-Button:

Im nächsten Fenster des Dialoges die Value ("User Roles") setzen:

Access Filter
Der Access Filter prüft, ob eine UserID vorhanden ist. Das ist in unserem Beispiel die Email, es könnte aber auch der Preferred Username genommen werden.
Des weiteren prüft der Access Filter, ob der Benutzer die erforderliche Rolle user hat.
Sind beide Bedingungen erfüllt, kann auf die Anwendung zugegriffen werden, ansonsten wird lediglich "Access denied" angezeigt.
package deringo.oneloginjavaappsample; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; @WebFilter("/*") public class AccessFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("AccessFilter init"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("AccessFilter doFilter"); HttpServletRequest httpRequest = (HttpServletRequest) request; String oidc_claim_email = httpRequest.getHeader("oidc_claim_email"); String oidc_claim_preferred_username = httpRequest.getHeader("oidc_claim_preferred_username"); String userid = oidc_claim_email; String oidc_claim_params = httpRequest.getHeader("oidc_claim_params"); Listroles = getRoles(oidc_claim_params); boolean hasRequiredRole = roles.contains("user"); if (!StringUtils.isBlank(userid) && hasRequiredRole) { // Grant access chain.doFilter(request, response); } else { response.getWriter().println("Access denied"); response.getWriter().close(); } } @Override public void destroy() { System.out.println("AccessFilter destroy"); } private static List getRoles(String oidc_claim_params) { List rolesList = new ArrayList<>(); try { JSONObject o = new JSONObject(oidc_claim_params); String rolesS = o.get("roles").toString(); String[] roles = StringUtils.split(rolesS, ";"); rolesList = Arrays.asList(roles); } catch (Exception e) { e.printStackTrace(); } return rolesList; } }
Docker
Über Docker Compose werden beide Server gestartet.
Über Port 80 ist der Reverse Proxy erreichbar, über Port 8080 direkt der Anwendungsserver.
version: '3.8' services: reverseproxy: build: ./reverseproxy hostname: reverseproxy volumes: - ./reverseproxy/public_html:/usr/local/apache2/htdocs - ${PWD}/reverseproxy/conf/reverseproxy_httpd.conf:/usr/local/apache2/conf/httpd.conf - ${PWD}/reverseproxy/conf/reverseproxy.conf:/usr/local/apache2/conf/reverseproxy.conf ports: - 80:80 sampleapp: build: ./oneloginjavaappsample hostname: sampleapp ports: - 8080:8080
Im Docker File des Anwendungsservers kopieren wir erst nur das Maven POM, lassen dann Maven bauen, um so die Abhängigkeiten herunterzuladen. Erst danach kopieren wir das gesamte Projekt in den Container und lassen die Anwendung bauen. So wird nach Code Änderungen, ohne Anpassungen in Maven, der Bau des Images beschleunigt, da nicht jedes Mal die Bibliotheken heruntergeladen werden müssen.
FROM tomcat:8.5-jdk8-openjdk-slim RUN apt update && apt install -y \ maven COPY pom.xml /app/pom.xml WORKDIR /app RUN mvn package COPY . /app RUN mvn package WORKDIR $CATALINA_HOME RUN mv /app/target/ROOT.war webapps EXPOSE 8080 CMD ["catalina.sh", "run"]
Test
Zugriff auf den Anwendungsserver über den Reverse Proxy nach Anmeldung:
http://localhost/private/test.html

Direkter Zugriff auf den Anwendungsserver ohne Anmeldung:
http://localhost:8080/index.html

GitHub
Die Dateien zu diesem Post sind im OneLogin-GitHub-Projekt unter version6 zu finden.