‏ ‏ ‎ ‏ ‏ ‎

1. Create Project

quarkus create app at.htl:http-filter-auth \
        --extension quarkus-resteasy \
                   ,quarkus-resteasy-jackson

result:

Looking for the newly published extensions in registry.quarkus.io
-----------
selected extensions:
- io.quarkus:quarkus-resteasy
- io.quarkus:quarkus-resteasy-jackson


applying codestarts...
📚 java
🔨 maven
📦 quarkus
📝 config-properties
🔧 tooling-dockerfiles
🔧 tooling-maven-wrapper
🚀 resteasy-codestart

-----------
Looking for the newly published extensions in registry.quarkus.io
-----------
selected extensions:
- io.quarkus:quarkus-resteasy
- io.quarkus:quarkus-resteasy-jackson


applying codestarts...
📚 java
🔨 maven
📦 quarkus
📝 config-properties
🔧 tooling-dockerfiles
🔧 tooling-maven-wrapper
🚀 resteasy-codestart

-----------
[SUCCESS] ✅  quarkus project has been successfully generated in:
--> /Users/stuetz/SynologyDrive/htl/skripten/themen/jakartaee-microprofile/quarkus/50-quarkus-security/quarkus-security-lecture-notes/labs/auth
-----------
Navigate into this directory and get started: quarkus dev
run project
quarkus dev --clean
access endpoint with curl
❯ curl http://localhost:8080/hello
Hello RESTEasy%
access endpoint with httpie
❯ http localhost:8080/hello
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
content-length: 14

Hello RESTEasy
first run
Figure 1. access endpoint with the rest-client of the IDE

2. Overview - Authentication and Authorization

authentication and authorization

3. First Usage of a ContainerRequestFilter

package at.htl.auth;

import io.quarkus.logging.Log;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.ext.Provider;

import java.io.IOException;

@Provider
@Priority(Priorities.AUTHENTICATION) (1)
public class AuthenticationFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        Log.info("Container Request Filter for authentication - Wer bin ich?");
    }
}
1 Die Priority legt die Aufrufreihenfolge der Filter fest. Die Authentifizierung muss als Erstes erfolgen.
  • Führt man neuerlich einen Request aus, so wird in der Console des Servers der Logeintrag angezeigt

    2024-09-28 17:51:18,515 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Container Request Filter for authentication - Wer bin ich?

4. Add Basic Auth to ContainerRequestFilter

Zunächst erstellen base64-codierte Credentials
❯ echo -n "john:doe" | base64
am9objpkb2U=
  • When you do echo "password" | md5, echo adds a newline to the string to be hashed, i.e. password\n. When you add the -n switch, it doesn’t, so only the characters password are hashed. (source)

Nun setzen wir einen GET-Request ab
GET http://localhost:8080/hello
Authorization: Basic am9objpkb2U=
gesendeter GET-Request (Tools | HTTP Client | 'Show HTTP Requests History')
GET http://localhost:8080/hello
Authorization: Basic am9objpkb2U=
User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2024.3 EAP
Accept-Encoding: br, deflate, gzip, x-gzip
Accept: */*
content-length: 0
Im Server-Log gibt es nun folgende Einträge
2024-09-28 18:16:37,704 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Container Request Filter for authentication - Wer bin ich?
2024-09-28 18:16:37,705 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Authorization=Basic am9objpkb2U=

4.1. Decodieren der Credentials

package at.htl.auth;

import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Base64;
import java.util.regex.Pattern;

@ApplicationScoped
public class Base64AuthenticationParser {
    private static final Pattern BASIC_AUTH_PATTERN = Pattern.compile("Basic (.*)");

    public static record Credentials(String username, String password) {}

    public Credentials parseAuthenticationHeader(String header) {
        if (header == null) {
            return null;
        }

        var matcher = BASIC_AUTH_PATTERN.matcher(header);
        if (!matcher.find()) {
            return null;
        }

        var encodedCredentials = matcher.group(1);
        var decodedCredentials = new String(Base64.getDecoder().decode(encodedCredentials));
        Log.info(decodedCredentials);

        var usernameAndPassword = decodedCredentials.split(":");
        return new Credentials(usernameAndPassword[0], usernameAndPassword[1]);
    }
}
package at.htl.auth;

import io.quarkus.logging.Log;
import jakarta.annotation.Priority;
import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;

import java.io.IOException;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    @Inject
    Base64AuthenticationParser base64AuthenticationParser;

    @Context
    ResourceInfo resourceInfo;


    public static final String CREDENTIALS = AuthenticationFilter.class.getSimpleName() + "_CREDENTIALS";

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {

        var annotation = resourceInfo
                .getResourceClass()
                .getAnnotation(PermitAll.class);

        Log.info("Container Request Filter for authentication - Wer bin ich?");
        Log.info("Authorization=" + ctx.getHeaderString("Authorization"));

        var credentials = base64AuthenticationParser
                .parseAuthenticationHeader(
                        ctx.getHeaderString("Authorization")
                );
        if (credentials != null) {

            Log.infof("credentials.username=%s, credentials.password=%s"
                    , credentials.username()
                    , credentials.password()
            );
            ctx.setProperty(CREDENTIALS, credentials);
        } else {
            ctx.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        }
    }
}
GET-Request mit Credentials
GET http://localhost:8080/hello
Authorization: Basic am9objpkb2U=
Response
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
content-length: 14

Hello RESTEasy
Im Server-Log gibt es nun folgende Einträge
2024-09-28 20:40:29,403 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Container Request Filter for authentication - Wer bin ich?
2024-09-28 20:40:29,403 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Authorization=Basic am9objpkb2U=
2024-09-28 20:40:29,404 INFO  [at.htl.aut.Base64AuthenticationParser] (executor-thread-1) john:doe
2024-09-28 20:40:29,405 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) credentials.username=john, credentials.password=doe
GET-Request ohne Credentials
GET http://localhost:8080/hello
Response
HTTP/1.1 401 Unauthorized
content-length: 0

<Response body is empty>
Im Server-Log gibt es nun folgende Einträge
2024-09-29 06:16:03,656 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Container Request Filter for authentication - Wer bin ich?
2024-09-29 06:16:03,659 INFO  [at.htl.aut.AuthenticationFilter] (executor-thread-1) Authorization=null

5. Sources