🚧 improvements
This commit is contained in:
parent
4342956d06
commit
bae5ede085
14
pom.xml
14
pom.xml
@ -43,6 +43,14 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-reactive-routes</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-orm-panache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
@ -61,6 +69,12 @@
|
||||
<version>4.5.14</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.smallrye</groupId>
|
||||
<artifactId>smallrye-jwt</artifactId>
|
||||
<version>4.6.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit</artifactId>
|
||||
|
||||
23
src/main/java/dev/dinauer/JwtUtils.java
Normal file
23
src/main/java/dev/dinauer/JwtUtils.java
Normal file
@ -0,0 +1,23 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import io.smallrye.jwt.auth.principal.DefaultJWTParser;
|
||||
import io.smallrye.jwt.auth.principal.ParseException;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public class JwtUtils
|
||||
{
|
||||
public static ZonedDateTime extractExpiresAt(String token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Instant.ofEpochSecond(new DefaultJWTParser().parseOnly(token).getExpirationTime()).atZone(ZoneOffset.UTC);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/main/java/dev/dinauer/oidcproxy/AccessToken.java
Normal file
7
src/main/java/dev/dinauer/oidcproxy/AccessToken.java
Normal file
@ -0,0 +1,7 @@
|
||||
package dev.dinauer.oidcproxy;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public record AccessToken(ZonedDateTime expiresAt, String token)
|
||||
{
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
package dev.dinauer.oidcproxy;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ForwardService
|
||||
{
|
||||
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
package dev.dinauer.oidcproxy;
|
||||
|
||||
import dev.dinauer.oidcproxy.callback.CallbackService;
|
||||
import dev.dinauer.oidcproxy.callback.SessionRepository;
|
||||
import dev.dinauer.oidcproxy.startup.ProxyRoute;
|
||||
import dev.dinauer.oidcproxy.startup.RouteService;
|
||||
import io.quarkus.vertx.web.Route;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.http.Cookie;
|
||||
import io.vertx.core.http.HttpMethod;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.Link;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.*;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ProxyResource
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProxyResource.class);
|
||||
|
||||
@Inject
|
||||
RouteService routeService;
|
||||
|
||||
@Inject
|
||||
SessionRepository sessionRepository;
|
||||
|
||||
@Inject
|
||||
ForwardService forwardService;
|
||||
|
||||
@Inject
|
||||
CallbackService callbackService;
|
||||
|
||||
@Route(path = "/callback", order = 0)
|
||||
public void callback(@Context RoutingContext context)
|
||||
{
|
||||
callbackService.get(context.response(), context.request());
|
||||
}
|
||||
|
||||
@Route(path = "/*", order = 1)
|
||||
public void proxy(@Context RoutingContext context)
|
||||
{
|
||||
List<String> requestSegments = Arrays.stream(context.request().path().split("/")).filter(item -> !StringUtils.isBlank(item)).toList();
|
||||
|
||||
Optional<ProxyRoute> routeOptional = routeService.match(requestSegments);
|
||||
if (routeOptional.isPresent())
|
||||
{
|
||||
ProxyRoute route = routeOptional.get();
|
||||
LOG.info("Matched route with target '{}'", route.target());
|
||||
try
|
||||
{
|
||||
byte[] body = extractBody(context);
|
||||
HttpResponse<byte[]> response = forward(context.request().headers(), context.request().method(), body, route.strategy(), extractSession(context.request().cookies()), route.target(), concat(dropRoute(route.segments(), requestSegments)));
|
||||
ResponseHandler.success(context, response);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.error("Error occurred on upstream.", e);
|
||||
ResponseHandler.error(context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("No route found for request path '{}'", context.request().path());
|
||||
ResponseHandler.notFound(context);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] extractBody(RoutingContext context)
|
||||
{
|
||||
if (context.body().buffer() != null)
|
||||
{
|
||||
return context.body().buffer().getBytes();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public HttpResponse<byte[]> forward(MultiMap headers, HttpMethod method, byte[] body, String strategy, String auth, String forwardRoot, String forwardPath) throws IOException, InterruptedException
|
||||
{
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(URI.create(forwardRoot + "/" + forwardPath));
|
||||
if (body != null)
|
||||
{
|
||||
builder.method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.method(method.name(), HttpRequest.BodyPublishers.noBody());
|
||||
}
|
||||
for (Map.Entry<String, String> entry : headers.entries())
|
||||
{
|
||||
try
|
||||
{
|
||||
builder.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// empty
|
||||
}
|
||||
}
|
||||
if (auth != null && Strings.CI.equals("OIDC", strategy))
|
||||
{
|
||||
builder.header("Authorization", String.format("Bearer %s", sessionRepository.get(auth)));
|
||||
}
|
||||
try(HttpClient client = HttpClient.newHttpClient())
|
||||
{
|
||||
return client.send(builder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> dropRoute(List<String> route, List<String> request)
|
||||
{
|
||||
List<String> requestSegments = new LinkedList<>(request);
|
||||
for (int i = 0; i < route.size(); i++)
|
||||
{
|
||||
requestSegments.removeFirst();
|
||||
}
|
||||
return requestSegments;
|
||||
}
|
||||
|
||||
private String concat(List<String> segments)
|
||||
{
|
||||
return String.join("/", segments);
|
||||
}
|
||||
|
||||
private String extractSession(Set<Cookie> cookies)
|
||||
{
|
||||
for (Cookie cookie : cookies)
|
||||
{
|
||||
if ("session".equals(cookie.getName()))
|
||||
{
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
package dev.dinauer.oidcproxy.callback;
|
||||
|
||||
import dev.dinauer.JwtUtils;
|
||||
import dev.dinauer.oidcproxy.AccessToken;
|
||||
import dev.dinauer.oidcproxy.callback.model.TokenResponse;
|
||||
import dev.dinauer.oidcproxy.session.SessionCache;
|
||||
import io.vertx.core.http.Cookie;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
@ -8,6 +11,8 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@ApplicationScoped
|
||||
@ -17,7 +22,7 @@ public class CallbackService
|
||||
OidcClient client;
|
||||
|
||||
@Inject
|
||||
SessionRepository sessionRepository;
|
||||
SessionCache sessionCache;
|
||||
|
||||
@ConfigProperty(name = "oidc.proxy.client.redirect")
|
||||
String redirectURI;
|
||||
@ -25,9 +30,12 @@ public class CallbackService
|
||||
public void get(HttpServerResponse response, HttpServerRequest request)
|
||||
{
|
||||
String code = request.params().get("code");
|
||||
TokenResponse token = client.exchangeAuthorizationCode(code);
|
||||
String sessionId = sessionRepository.add(token.accessToken());
|
||||
response.addCookie(Cookie.cookie("session", sessionId).setHttpOnly(true).setSecure(true).setPath("/").setMaxAge((int) (token.expiresAt() - ZonedDateTime.now().toEpochSecond())));
|
||||
TokenResponse oidcResponse = client.exchangeAuthorizationCode(code);
|
||||
String sessionId = sessionCache.add(oidcResponse.accessToken(), oidcResponse.refreshToken(), Instant.ofEpochSecond(oidcResponse.expiresAt()).atZone(ZoneOffset.UTC));
|
||||
|
||||
int cookieExpiry = (int) (JwtUtils.extractExpiresAt(oidcResponse.refreshToken()).toEpochSecond() - ZonedDateTime.now().toEpochSecond());
|
||||
response.addCookie(Cookie.cookie("session", sessionId).setHttpOnly(true).setSecure(true).setPath("/").setMaxAge(cookieExpiry));
|
||||
response.addCookie(Cookie.cookie("identity", oidcResponse.idToken()).setSecure(true).setPath("/").setMaxAge(cookieExpiry));
|
||||
response.setStatusCode(302);
|
||||
response.putHeader("Location", redirectURI);
|
||||
response.send();
|
||||
|
||||
@ -41,7 +41,7 @@ public class OidcClient
|
||||
try
|
||||
{
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() == 200)
|
||||
if (response.statusCode() < 400)
|
||||
{
|
||||
return OBJECT_MAPPER.readValue(response.body(), TokenResponse.class);
|
||||
}
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
package dev.dinauer.oidcproxy.callback;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SessionRepository
|
||||
{
|
||||
private final Map<String, String> tokens = new HashMap<>();
|
||||
|
||||
public String add(String token)
|
||||
{
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
tokens.put(sessionId, token);
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String get(String sessionId)
|
||||
{
|
||||
String token = tokens.get(sessionId);
|
||||
if (token != null)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package dev.dinauer.oidcproxy.callback.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record TokenResponse(@JsonProperty("access_token") String accessToken, @JsonProperty("expires_at") Long expiresAt)
|
||||
public record TokenResponse(@JsonProperty("access_token") String accessToken, @JsonProperty("refresh_token") String refreshToken, @JsonProperty("id_token") String idToken, @JsonProperty("expires_at") Long expiresAt)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
package dev.dinauer.oidcproxy.proxy;
|
||||
|
||||
import dev.dinauer.oidcproxy.proxy.exception.ProxyHttpException;
|
||||
import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
|
||||
import dev.dinauer.oidcproxy.session.SessionCache;
|
||||
import dev.dinauer.oidcproxy.proxy.header.HeaderFilter;
|
||||
import dev.dinauer.oidcproxy.request.HttpRequestBuilder;
|
||||
import io.vertx.core.http.Cookie;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.*;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ForwardService
|
||||
{
|
||||
private static final String AUTH_HEADER = "Authorization";
|
||||
private static final HttpClient CLIENT = HttpClient.newHttpClient();
|
||||
|
||||
@Inject
|
||||
SessionCache sessionCache;
|
||||
|
||||
@Inject
|
||||
HeaderFilter headerFilter;
|
||||
|
||||
public HttpResponse<byte[]> send(RoutingContext context, String route, String strategy) throws IOException, InterruptedException, ProxyHttpException, TokenNotFoundException
|
||||
{
|
||||
HttpRequestBuilder builder = HttpRequestBuilder.create();
|
||||
builder.setUri(route);
|
||||
builder.setParams(context.request().params().entries());
|
||||
builder.setMethod(context.request().method().toString());
|
||||
builder.setHeaders(headerFilter.filter(context.request(), strategy));
|
||||
builder.setBody(extractBody(context));
|
||||
|
||||
HttpResponse<byte[]> response = CLIENT.send(builder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (response.statusCode() < 400)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
throw new ProxyHttpException(response.statusCode());
|
||||
}
|
||||
|
||||
private List<Map.Entry<String, String>> buildHeaders(List<Map.Entry<String, String>> request, Set<Cookie> cookies, String strategy) throws TokenNotFoundException
|
||||
{
|
||||
List<String> headerNames = request.stream().map(Map.Entry::getKey).toList();
|
||||
if (!headerNames.contains(AUTH_HEADER) && "OIDC".equals(strategy) && cookies.size() == 1)
|
||||
{
|
||||
String session = cookies.iterator().next().getValue();
|
||||
List<Map.Entry<String, String>> headers = new LinkedList<>(request);
|
||||
headers.add(Map.entry(AUTH_HEADER, sessionCache.get(session)));
|
||||
return headers;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private byte[] extractBody(RoutingContext context)
|
||||
{
|
||||
if (context.body().buffer() != null)
|
||||
{
|
||||
return context.body().buffer().getBytes();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
113
src/main/java/dev/dinauer/oidcproxy/proxy/ProxyResource.java
Normal file
113
src/main/java/dev/dinauer/oidcproxy/proxy/ProxyResource.java
Normal file
@ -0,0 +1,113 @@
|
||||
package dev.dinauer.oidcproxy.proxy;
|
||||
|
||||
import dev.dinauer.oidcproxy.callback.CallbackService;
|
||||
import dev.dinauer.oidcproxy.proxy.exception.ProxyHttpException;
|
||||
import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
|
||||
import dev.dinauer.oidcproxy.session.SessionCache;
|
||||
import dev.dinauer.oidcproxy.startup.PathConverter;
|
||||
import dev.dinauer.oidcproxy.startup.ProxyRoute;
|
||||
import dev.dinauer.oidcproxy.startup.RouteService;
|
||||
import io.quarkus.vertx.web.Route;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
import io.vertx.core.http.Cookie;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.*;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ProxyResource
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProxyResource.class);
|
||||
|
||||
@Inject
|
||||
RouteService routeService;
|
||||
|
||||
@Inject
|
||||
ForwardService forwardService;
|
||||
|
||||
@Inject
|
||||
CallbackService callbackService;
|
||||
|
||||
@Route(path = "/auth/callback", order = 0)
|
||||
@Blocking
|
||||
public void callback(@Context RoutingContext context)
|
||||
{
|
||||
callbackService.get(context.response(), context.request());
|
||||
}
|
||||
|
||||
@Route(path = "/auth/logout", order = 1)
|
||||
@Blocking
|
||||
public void logout(@Context HttpServerResponse response)
|
||||
{
|
||||
response.addCookie(Cookie.cookie("session", "").setMaxAge(0).setPath("/").setHttpOnly(true).setSecure(true));
|
||||
response.setStatusCode(302);
|
||||
response.putHeader("Location", "http://localhost:3000");
|
||||
response.send();
|
||||
}
|
||||
|
||||
@Route(path = "/*", order = 2)
|
||||
@Blocking
|
||||
public void proxy(@Context RoutingContext context)
|
||||
{
|
||||
HttpServerRequest request = context.request();
|
||||
|
||||
List<String> requestSegments = PathConverter.toSegments(request.path());
|
||||
|
||||
Optional<ProxyRoute> routeOptional = routeService.match(requestSegments);
|
||||
if (routeOptional.isPresent())
|
||||
{
|
||||
ProxyRoute route = routeOptional.get();
|
||||
LOG.info("Matched route with target '{}'", route.target());
|
||||
try
|
||||
{
|
||||
String targetPath = PathConverter.toPath(dropPrefix(route.segments(), requestSegments));
|
||||
String targetURI = route.target() + targetPath;
|
||||
HttpResponse<byte[]> response = forwardService.send(context, targetURI, route.strategy());
|
||||
ResponseHandler.success(context, response);
|
||||
}
|
||||
catch (ProxyHttpException e)
|
||||
{
|
||||
LOG.error("Upstream returned error status {}.", e.getStatusCode(), e);
|
||||
ResponseHandler.error(context, e.getStatusCode());
|
||||
}
|
||||
catch (TokenNotFoundException e)
|
||||
{
|
||||
LOG.error("Token not found.", e);
|
||||
ResponseHandler.error(context, 401);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
LOG.error("Proxy request was interrupted, returning 503.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
ResponseHandler.error(context, 503);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.error("Error occurred on upstream.", e);
|
||||
ResponseHandler.error(context, 502);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("No route found for request path '{}'", context.request().path());
|
||||
ResponseHandler.notFound(context);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> dropPrefix(List<String> route, List<String> request)
|
||||
{
|
||||
for (int i = 0; i < route.size(); i++)
|
||||
{
|
||||
request.removeFirst();
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package dev.dinauer.oidcproxy;
|
||||
package dev.dinauer.oidcproxy.proxy;
|
||||
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
@ -27,9 +27,9 @@ public class ResponseHandler
|
||||
context.response().send(Buffer.buffer(response.body()));
|
||||
}
|
||||
|
||||
public static void error(RoutingContext context)
|
||||
public static void error(RoutingContext context, int statusCode)
|
||||
{
|
||||
context.response().setStatusCode(502);
|
||||
context.response().setStatusCode(statusCode);
|
||||
context.response().send();
|
||||
}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package dev.dinauer.oidcproxy.proxy.exception;
|
||||
|
||||
public class ProxyHttpException extends Exception
|
||||
{
|
||||
private final int statusCode;
|
||||
|
||||
public ProxyHttpException(int statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package dev.dinauer.oidcproxy.proxy.exception;
|
||||
|
||||
public class TokenNotFoundException extends Exception
|
||||
{
|
||||
public TokenNotFoundException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package dev.dinauer.oidcproxy.proxy.header;
|
||||
|
||||
import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
|
||||
import dev.dinauer.oidcproxy.proxy.header.strategy.OidcStrategy;
|
||||
import dev.dinauer.oidcproxy.session.SessionCache;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
import io.vertx.core.http.Cookie;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ApplicationScoped
|
||||
public class HeaderFilter
|
||||
{
|
||||
private static final List<String> HOP2HOP = List.of("Keep-Alive", "Transfer-Encoding", "TE", "Connection", "Trailer", "Upgrade", "Proxy-Authenticate", "Proxy-Authorization");
|
||||
|
||||
@Inject
|
||||
SessionCache sessionCache;
|
||||
|
||||
@Inject
|
||||
OidcStrategy oidcStrategy;
|
||||
|
||||
public List<Map.Entry<String, String>> filter(HttpServerRequest request, String strategy) throws TokenNotFoundException
|
||||
{
|
||||
List<Map.Entry<String, String>> headers = filterHop2HopHeaders(request.headers().entries());
|
||||
if ("OIDC".equals(strategy))
|
||||
{
|
||||
headers = oidcStrategy.filter(getAccessToken(request), headers);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String getAccessToken(HttpServerRequest request) throws TokenNotFoundException
|
||||
{
|
||||
for (Cookie cookie : request.cookies())
|
||||
{
|
||||
if ("session".equals(cookie.getName()))
|
||||
{
|
||||
String session = cookie.getValue();
|
||||
return sessionCache.get(session);
|
||||
}
|
||||
}
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
private List<Map.Entry<String, String>> filterHop2HopHeaders(List<Map.Entry<String, String>> input)
|
||||
{
|
||||
List<Map.Entry<String, String>> result = new LinkedList<>();
|
||||
for (Map.Entry<String, String> header : input)
|
||||
{
|
||||
if (!HOP2HOP.contains(header.getKey()))
|
||||
{
|
||||
result.add(header);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package dev.dinauer.oidcproxy.proxy.header.strategy;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ApplicationScoped
|
||||
public class NoneStrategy
|
||||
{
|
||||
public List<Map.Entry<String, String>> filter(List<Map.Entry<String, String>> input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package dev.dinauer.oidcproxy.proxy.header.strategy;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ApplicationScoped
|
||||
public class OidcStrategy
|
||||
{
|
||||
private static final String AUTH_HEADER = "Authorization";
|
||||
|
||||
public List<Map.Entry<String, String>> filter(String jwt, List<Map.Entry<String, String>> input)
|
||||
{
|
||||
if (!hasAuthHeader(input))
|
||||
{
|
||||
input.add(Map.entry(AUTH_HEADER, String.format("Bearer %s", jwt)));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
private boolean hasAuthHeader(List<Map.Entry<String, String>> input)
|
||||
{
|
||||
for (Map.Entry<String, String> header : input)
|
||||
{
|
||||
if (Strings.CI.equals(AUTH_HEADER, header.getKey()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package dev.dinauer.oidcproxy.request;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HttpRequestBuilder
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpRequestBuilder.class);
|
||||
|
||||
private String method;
|
||||
private String uri;
|
||||
private List<Map.Entry<String, String>> params;
|
||||
private List<Map.Entry<String, String>> headers;
|
||||
private byte[] body;
|
||||
|
||||
public static HttpRequestBuilder create()
|
||||
{
|
||||
return new HttpRequestBuilder();
|
||||
}
|
||||
|
||||
public void setMethod(String method)
|
||||
{
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public void setUri(String uri)
|
||||
{
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public void setParams(List<Map.Entry<String, String>> params)
|
||||
{
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public void setHeaders(List<Map.Entry<String, String>> headers)
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public void setBody(byte[] body)
|
||||
{
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public HttpRequest build()
|
||||
{
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder();
|
||||
builder.uri(buildURI(this.uri, this.params));
|
||||
builder.method(method, buildBody(body));
|
||||
|
||||
if (this.headers != null)
|
||||
{
|
||||
for (Map.Entry<String, String> element : this.headers)
|
||||
{
|
||||
try
|
||||
{
|
||||
builder.setHeader(element.getKey(), element.getValue());
|
||||
LOG.info("added header " + element.getKey());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.info("Failed to add header.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static URI buildURI(String uri, List<Map.Entry<String, String>> params)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (params != null && !params.isEmpty())
|
||||
{
|
||||
String queryParams = params.stream().map(entry -> String.format("%s=%s", URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8), URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))).collect(Collectors.joining("&"));
|
||||
return new URI(String.format("%s?%s", uri, queryParams));
|
||||
}
|
||||
return new URI(uri);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpRequest.BodyPublisher buildBody(byte[] body)
|
||||
{
|
||||
if (body != null)
|
||||
{
|
||||
return HttpRequest.BodyPublishers.ofByteArray(body);
|
||||
}
|
||||
return HttpRequest.BodyPublishers.noBody();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "access_token")
|
||||
public class AccessTokenEntity
|
||||
{
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(name = "expires_at")
|
||||
private ZonedDateTime expiresAt;
|
||||
|
||||
@Column(columnDefinition = "text")
|
||||
private String token;
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public AccessTokenEntity setId(String id)
|
||||
{
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ZonedDateTime getExpiresAt()
|
||||
{
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
public AccessTokenEntity setExpiresAt(ZonedDateTime expiresAt)
|
||||
{
|
||||
this.expiresAt = expiresAt;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getToken()
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
public AccessTokenEntity setToken(String token)
|
||||
{
|
||||
this.token = token;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ApplicationScoped
|
||||
public class AccessTokenRepository implements PanacheRepositoryBase<AccessTokenEntity, String>
|
||||
{
|
||||
public List<AccessTokenEntity> findExpiresBefore(ZonedDateTime timestamp)
|
||||
{
|
||||
return list("expiresAt <= :timestamp", Map.ofEntries(Map.entry("timestamp", timestamp)));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
@ApplicationScoped
|
||||
public class EncryptUtils
|
||||
{
|
||||
private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
|
||||
private static final int IV_LENGTH = 8;
|
||||
private static final int AUTH_TAG_LENGTH = 128;
|
||||
|
||||
private final SecretKey key;
|
||||
|
||||
public EncryptUtils(@ConfigProperty(name = "oidc.proxy.crypto.secret") String secret)
|
||||
{
|
||||
try
|
||||
{
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(secret.toCharArray(), secret.getBytes(), 256_000, 256);
|
||||
this.key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
|
||||
}
|
||||
catch (NoSuchAlgorithmException | InvalidKeySpecException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String encrypt(String input) throws Exception
|
||||
{
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(AUTH_TAG_LENGTH, iv));
|
||||
|
||||
byte[] encrypted = cipher.doFinal(input.getBytes((StandardCharsets.UTF_8)));
|
||||
|
||||
byte[] combined = new byte[iv.length + encrypted.length];
|
||||
System.arraycopy(iv, 0, combined, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
|
||||
|
||||
return Base64.getEncoder().encodeToString(combined);
|
||||
}
|
||||
|
||||
public String decrypt(String ciphertext) throws Exception
|
||||
{
|
||||
byte[] combined = Base64.getDecoder().decode(ciphertext);
|
||||
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
byte[] encrypted = new byte[combined.length - IV_LENGTH];
|
||||
System.arraycopy(combined, 0, iv, 0, iv.length);
|
||||
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AUTH_TAG_LENGTH, iv));
|
||||
|
||||
return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "refresh_token")
|
||||
public class RefreshTokenEntity
|
||||
{
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(name = "expires_at")
|
||||
private ZonedDateTime expiresAt;
|
||||
|
||||
@Column(columnDefinition = "text")
|
||||
private String token;
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public RefreshTokenEntity setId(String id)
|
||||
{
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ZonedDateTime getExpiresAt()
|
||||
{
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
public RefreshTokenEntity setExpiresAt(ZonedDateTime expiresAt)
|
||||
{
|
||||
this.expiresAt = expiresAt;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RefreshTokenEntity setToken(String token)
|
||||
{
|
||||
this.token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getToken()
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ApplicationScoped
|
||||
public class RefreshTokenRepository implements PanacheRepositoryBase<RefreshTokenEntity, String>
|
||||
{
|
||||
public List<RefreshTokenEntity> findExpiresBefore(ZonedDateTime timestamp)
|
||||
{
|
||||
return list("expiresAt <= :timestamp", Map.ofEntries(Map.entry("timestamp", timestamp)));
|
||||
}
|
||||
}
|
||||
104
src/main/java/dev/dinauer/oidcproxy/session/SessionCache.java
Normal file
104
src/main/java/dev/dinauer/oidcproxy/session/SessionCache.java
Normal file
@ -0,0 +1,104 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import dev.dinauer.oidcproxy.AccessToken;
|
||||
import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
import io.quarkus.runtime.Startup;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.enterprise.context.control.ActivateRequestContext;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SessionCache
|
||||
{
|
||||
private final Map<String, AccessToken> tokens = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@Inject
|
||||
SessionService sessionService;
|
||||
|
||||
@Inject
|
||||
EncryptUtils encryptUtils;
|
||||
@Inject
|
||||
AccessTokenRepository accessTokenRepository;
|
||||
|
||||
@Startup
|
||||
@ActivateRequestContext
|
||||
void housekeeping()
|
||||
{
|
||||
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
|
||||
LOG.info("Running housekeeping...");
|
||||
List<AccessTokenEntity> sessions = accessTokenRepository.findExpiresBefore(ZonedDateTime.now().plusMinutes(2));
|
||||
for (AccessTokenEntity session : sessions)
|
||||
{
|
||||
QuarkusTransaction.begin();
|
||||
tokens.remove(session.getId());
|
||||
try
|
||||
{
|
||||
accessTokenRepository.delete(session);
|
||||
QuarkusTransaction.commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
QuarkusTransaction.rollback();
|
||||
}
|
||||
}
|
||||
}, 0, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public String add(String accessToken, String refreshToken, ZonedDateTime expiresAt)
|
||||
{
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
sessionService.create(sessionId, expiresAt, accessToken, refreshToken);
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String get(String sessionId) throws TokenNotFoundException
|
||||
{
|
||||
String session = toHash(sessionId);
|
||||
AccessToken token = tokens.get(session);
|
||||
if (token != null)
|
||||
{
|
||||
return token.token();
|
||||
}
|
||||
AccessTokenEntity accessTokenEntity = accessTokenRepository.findById(session);
|
||||
if (session != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
String accessToken = encryptUtils.decrypt(accessTokenEntity.getToken());
|
||||
tokens.put(session, new AccessToken(accessTokenEntity.getExpiresAt(), accessToken));
|
||||
return accessToken;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
throw new TokenNotFoundException();
|
||||
}
|
||||
|
||||
private String toHash(String sessionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Base64.getEncoder().encodeToString( MessageDigest.getInstance("SHA-256").digest(sessionId.getBytes()));
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import dev.dinauer.oidcproxy.AccessToken;
|
||||
import io.smallrye.jwt.auth.principal.DefaultJWTParser;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Base64;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SessionService
|
||||
{
|
||||
@Inject
|
||||
EncryptUtils encryptUtils;
|
||||
|
||||
@Inject
|
||||
AccessTokenRepository accessTokenRepository;
|
||||
|
||||
@Inject
|
||||
RefreshTokenRepository refreshTokenRepository;
|
||||
|
||||
@Transactional
|
||||
public void create(String sessionId, ZonedDateTime sessionExpiresAt, String accessToken, String refreshToken)
|
||||
{
|
||||
String sessionHash = toHash(sessionId);
|
||||
try
|
||||
{
|
||||
JsonWebToken token = new DefaultJWTParser().parseOnly(accessToken);
|
||||
ZonedDateTime accessTokenExpiresAt = Instant.ofEpochSecond(token.getExpirationTime()).atZone(ZoneOffset.UTC);
|
||||
accessTokenRepository.persist(new AccessTokenEntity().setId(sessionHash).setToken(encryptUtils.encrypt(accessToken)).setExpiresAt(accessTokenExpiresAt));
|
||||
refreshTokenRepository.persist(new RefreshTokenEntity().setId(sessionHash).setToken(encryptUtils.encrypt(refreshToken)).setExpiresAt(sessionExpiresAt));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String toHash(String sessionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Base64.getEncoder().encodeToString( MessageDigest.getInstance("SHA-256").digest(sessionId.getBytes()));
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package dev.dinauer.oidcproxy.startup;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PathConverter
|
||||
{
|
||||
private static final String SEPARATOR = "/";
|
||||
|
||||
public static List<String> toSegments(String path)
|
||||
{
|
||||
if (StringUtils.isBlank(path))
|
||||
{
|
||||
return new LinkedList<>();
|
||||
}
|
||||
return new LinkedList<>(Arrays.stream(path.split(SEPARATOR)).filter(item -> !StringUtils.isBlank(item)).toList());
|
||||
}
|
||||
|
||||
public static String toPath(List<String> segments)
|
||||
{
|
||||
if (isEmpty(segments))
|
||||
{
|
||||
return SEPARATOR;
|
||||
}
|
||||
return SEPARATOR + String.join(SEPARATOR, segments);
|
||||
}
|
||||
|
||||
public static String normalize(String path)
|
||||
{
|
||||
return Strings.CI.removeEnd(path, SEPARATOR);
|
||||
}
|
||||
|
||||
private static boolean isEmpty(List<String> list)
|
||||
{
|
||||
return list == null || list.isEmpty();
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,21 @@
|
||||
package dev.dinauer.oidcproxy.startup;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record ProxyRoute(String path, String target, String strategy)
|
||||
{
|
||||
public String path()
|
||||
{
|
||||
return Strings.CI.removeEnd(target, "/");
|
||||
return PathConverter.normalize(this.path);
|
||||
}
|
||||
|
||||
public String target()
|
||||
{
|
||||
return Strings.CI.removeEnd(target, "/");
|
||||
return PathConverter.normalize(this.target);
|
||||
}
|
||||
|
||||
public List<String> segments()
|
||||
{
|
||||
return Stream.of(path.split("/")).filter(item -> !StringUtils.isBlank(item)).toList();
|
||||
return PathConverter.toSegments(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,10 @@ public class RouteService
|
||||
List<ProxyRoute> result = new LinkedList<>();
|
||||
for (ConfigRoute route : rules.routes())
|
||||
{
|
||||
if (StringUtils.isBlank(route.strategy()))
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (StringUtils.isBlank(rules.root()))
|
||||
{
|
||||
result.add(new ProxyRoute(route.path(), route.target(), route.strategy()));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package dev.dinauer.oidcproxy.startup.model;
|
||||
|
||||
import dev.dinauer.oidcproxy.startup.PathConverter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
@ -8,10 +9,6 @@ public record ConfigRoute(String path, String target, String strategy)
|
||||
{
|
||||
public List<String> segments()
|
||||
{
|
||||
if (StringUtils.isBlank(path))
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
return List.of(path.split("/"));
|
||||
return PathConverter.toSegments(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package dev.dinauer.oidcproxy.startup.model;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import dev.dinauer.oidcproxy.startup.PathConverter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -8,10 +8,6 @@ public record ConfigRules(String root, List<ConfigRoute> routes)
|
||||
{
|
||||
public List<String> segments()
|
||||
{
|
||||
if (StringUtils.isBlank(root))
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
return List.of(root.split("/"));
|
||||
return PathConverter.toSegments(this.root);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,13 @@ oidc.proxy.client.id=backend
|
||||
oidc.proxy.client.secret=backend
|
||||
oidc.proxy.client.redirect=http://localhost:3000
|
||||
|
||||
%dev.oidc.proxy.routes.config.location=/home/andreas/Documents/dev/oidc-proxy/src/main/resources/routes.yaml
|
||||
%test,dev.oidc.proxy.routes.config.location=/home/andreas/Documents/dev/oidc-proxy/src/main/resources/routes.yaml
|
||||
%prod.oidc.proxy.routes.config.location=/var/lib/oidc-proxy/routes.yaml
|
||||
|
||||
%test,dev.quarkus.hibernate-orm.schema-management.strategy=drop-and-create
|
||||
|
||||
%dev,test.quarkus.datasource.username=postgres
|
||||
%dev,test.quarkus.datasource.password=postgres
|
||||
%dev,test.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=oidc-proxy
|
||||
|
||||
%dev,test.oidc.proxy.crypto.secret=test
|
||||
@ -2,6 +2,6 @@ routes:
|
||||
- path: /api
|
||||
target: http://localhost:8081
|
||||
strategy: OIDC
|
||||
- path: /
|
||||
- path: /example
|
||||
target: http://example.com
|
||||
strategy: NONE
|
||||
strategy: OIDC
|
||||
@ -0,0 +1,28 @@
|
||||
package dev.dinauer.oidcproxy.session;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
@QuarkusTest
|
||||
public class EncryptUtilsTest
|
||||
{
|
||||
@Inject
|
||||
EncryptUtils encryptUtils;
|
||||
|
||||
@Test
|
||||
void test() throws Exception
|
||||
{
|
||||
String helloWorld = "Hello World!";
|
||||
|
||||
String encrypted = encryptUtils.encrypt(helloWorld);
|
||||
String decrypted = encryptUtils.decrypt(encrypted);
|
||||
|
||||
Assertions.assertEquals(helloWorld, decrypted);
|
||||
Assertions.assertNotEquals(helloWorld, new String(Base64.getDecoder().decode(encrypted), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user