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
Figure 1. access endpoint with the rest-client of the IDE
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 characterspassword
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