In my application I have a custom ContainerRequestFilter implementation which replaces the SecurityContext in the ContainerRequestContext. At the same time there is a io.quarkus.resteasy.runtime.SecurityContextFilter in quarkus-resteasy which replaces the actual SecurityIdentity with an implementation that uses modified security context (added in this commit).
Since my implementation of SecurityContext which is almost the same as QuarkusResteasySecurityContext invokes methods of SecurityIdentity this creates an infinity recursion and provokes StackOverflowError: SecurityContext calls SecurityIdentity > SecurityIdentity calls SecurityContext.
Is this a potential bug or is there any workaround how can I solve this issue? All examples of my implementations of filter and context provided. Also example project with a test that will failed due to StackOverflowError will be attached.
Expected behavior
No response
Actual behavior
No response
How to Reproduce?
Custom filter example:
@Provider
@PreMatching
public class CustomFilter implements ContainerRequestFilter {
@Inject
CurrentVertxRequest currentVertxRequest;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
SecurityContext securityContext = new CustomSecurityContext(
requestContext,
currentVertxRequest.getCurrent(),
List.of("admin")
);
requestContext.setSecurityContext(securityContext);
}
}
Custom security context example:
public class CustomSecurityContext implements SecurityContext {
private final ContainerRequestContext requestContext;
private final RoutingContext routingContext;
private final List<String> roles;
public CustomSecurityContext(ContainerRequestContext requestContext, RoutingContext routingContext, List<String> roles) {
this.requestContext = requestContext;
this.routingContext = routingContext;
this.roles = roles;
}
@Override
public Principal getUserPrincipal() {
QuarkusHttpUser user = (QuarkusHttpUser) routingContext.user();
return user != null && !user.getSecurityIdentity().isAnonymous() // StackOverflowError
? user.getSecurityIdentity().getPrincipal() // StackOverflowError
: null;
}
@Override
public boolean isUserInRole(String role) {
SecurityIdentity user = CurrentIdentityAssociation.current();
System.out.println(user.isAnonymous()); // StackOverflowError
if (role.equals("**")) {
return !user.isAnonymous(); // StackOverflowError
} else {
return roles.contains(role);
}
}
@Override
public boolean isSecure() {
return true;
}
@Override
public String getAuthenticationScheme() {
String authorizationValue = requestContext.getHeaders().getFirst("Authorization");
if (authorizationValue == null) {
return null;
} else {
return authorizationValue.split(" ")[0].trim();
}
}
}
Resteasy SecurityContextFilter that replaces the SecurityIdentity:
@PreMatching
@Priority(Priorities.USER + 1)
@Provider
public class SecurityContextFilter implements ContainerRequestFilter {
@Inject
SecurityIdentity old;
@Inject
CurrentIdentityAssociation currentIdentityAssociation;
@Inject
RoutingContext routingContext;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
SecurityContext modified = requestContext.getSecurityContext();
if (modified instanceof ServletSecurityContext || modified instanceof QuarkusResteasySecurityContext) {
//an original security context, it has not been modified
return;
}
Set<Credential> oldCredentials = old.getCredentials();
Map<String, Object> oldAttributes = old.getAttributes();
SecurityIdentity newIdentity = new SecurityIdentity() {
@Override
public Principal getPrincipal() {
return modified.getUserPrincipal();
}
@Override
public boolean isAnonymous() {
return modified.getUserPrincipal() == null;
}
@Override
public Set<String> getRoles() {
throw new UnsupportedOperationException(
"retrieving all roles not supported when JAX-RS security context has been replaced");
}
@Override
public boolean hasRole(String role) {
return modified.isUserInRole(role);
}
@Override
public <T extends Credential> T getCredential(Class<T> credentialType) {
for (Credential cred : getCredentials()) {
if (credentialType.isAssignableFrom(cred.getClass())) {
return (T) cred;
}
}
return null;
}
@Override
public Set<Credential> getCredentials() {
return oldCredentials;
}
@Override
public <T> T getAttribute(String name) {
return (T) oldAttributes.get(name);
}
@Override
public Map<String, Object> getAttributes() {
return oldAttributes;
}
@Override
public Uni<Boolean> checkPermission(Permission permission) {
return Uni.createFrom().nullItem();
}
};
routingContext.setUser(new QuarkusHttpUser(newIdentity));
currentIdentityAssociation.setIdentity(newIdentity);
}
}
Describe the bug
In my application I have a custom
ContainerRequestFilter
implementation which replaces theSecurityContext
in theContainerRequestContext
. At the same time there is aio.quarkus.resteasy.runtime.SecurityContextFilter
inquarkus-resteasy
which replaces the actualSecurityIdentity
with an implementation that uses modified security context (added in this commit).Since my implementation of
SecurityContext
which is almost the same asQuarkusResteasySecurityContext
invokes methods ofSecurityIdentity
this creates an infinity recursion and provokesStackOverflowError
:SecurityContext
callsSecurityIdentity
>SecurityIdentity
callsSecurityContext
.Is this a potential bug or is there any workaround how can I solve this issue? All examples of my implementations of filter and context provided. Also example project with a test that will failed due to
StackOverflowError
will be attached.Expected behavior
No response
Actual behavior
No response
How to Reproduce?
Custom filter example:
Custom security context example:
Resteasy
SecurityContextFilter
that replaces theSecurityIdentity
:Minimal example project: quarkus-custom-security-context.zip
Output of
uname -a
orver
Linux laptop 6.8.0-47-generic #47~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Oct 2 16:16:55 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
Output of
java -version
openjdk version "17.0.9" 2023-10-17 LTS
Quarkus version or git rev
3.14.3
Build tool (ie. output of
mvnw --version
orgradlew --version
)Gradle 8.11-rc-1
Additional information
No response