Categories
Development Java

OneLogin AccessFilter

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");
		List<String> roles = 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<String> getRoles(String oidc_claim_params) {
		List<String> 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.

Leave a Reply

Your email address will not be published. Required fields are marked *