micronaut-projects / micronaut-security

The official Micronaut security solution
Apache License 2.0
166 stars 122 forks source link

Since Micronaut Security 4.6.8 route match is randomly null in the SecurityFilter #1683

Open loicmathieu opened 1 month ago

loicmathieu commented 1 month ago

Expected Behavior

Migrating to 4.3.4 to 4.3.7 should not change the behavior of the Micronaut SecurityFilter.

Actual Behaviour

After migrating from 4.3.4 to 4.3.7, we started to see randomly routeMatch request attribute being null in the Micronaut SecurityFilter.

This has been seen using a debugger, see the following screenshot that show that routeMatch is null for a request GET /api.v1/demo/config but this endpoint is defined in our application so there should be a route match.

Dowgrading to 4.3.4 fixes the issue.

route-match-issue

Steps To Reproduce

No response

Environment Information

Java 17 Linux

Example Application

No response

Version

4.3.7

loicmathieu commented 1 month ago

The config endpoint is defined as is:

@Slf4j
@Controller("/api/v1")
public class MiscController {
    @Get("{/tenant}/configs")
    @ExecuteOn(TaskExecutors.IO)
    @Operation(tags = {"Misc"}, summary = "Get current configurations")
    public Configuration configuration() throws JsonProcessingException {
        // ...
    }
}
graemerocher commented 1 month ago

don't see anything related to this other than documenting the route match behaviour in the diff https://github.com/micronaut-projects/micronaut-core/compare/v4.3.4...v4.3.7

Do you have steps to reproduce?

loicmathieu commented 1 month ago

@graemerocher indeed there isn't anything that seems to be able to cause such issue. We are rolling back to the old version of Micronaut, I'll confirm tomorrow if it fixes the issue then I'll try to create a reproducer but as it seems "random" this may be hard to reproduce.

loicmathieu commented 1 month ago

I can confirm that rolling back to 4.3.4 works in the environment where it causes the issue. I'll try to create a reproducer but as it seems "random" this may be hard to reproduce.

yawkat commented 1 month ago

FYI it's likely not core v4.3.4...v4.3.7 you need to look at, you need to look at the equivalent micronaut-core versions for those platform versions instead.

graemerocher commented 1 month ago

so yeah this is the diff so it is between 4.3.9 and 4.3.12 of core

graemerocher commented 1 month ago

https://github.com/micronaut-projects/micronaut-platform/compare/v4.3.4..v4.3.7

graemerocher commented 1 month ago

not sure if there is much relevant there either https://github.com/micronaut-projects/micronaut-core/compare/v4.3.9...v4.3.12 🤔

sdelamo commented 1 month ago

I think I reproduced this in a project. I don't have a reproducer yet. However, upgrading an application that uses session based authentication from 4.3.4 to 4.4.0 I started to unable login in production. Downgraded solved the issue.

not sure if there is much relevant there either v4.3.9...v4.3.12 🤔

Maybe something here: https://github.com/micronaut-projects/micronaut-core/commit/1ab87c5729965c7f3faef519ca544c874aa3ab1d This commits seems the only one touching the Router.

graemerocher commented 1 month ago

@sdelamo can you find that commit where the regression exists?

sdelamo commented 1 month ago

For my application, the solution was to force the usage of the io.micronaut.http.cookie.DefaultServerCookieEncoder via the creation of a file:

src/main/resources/META-INF/services/io.micronaut.http.cookie.ServerCookieEncoder

With content:

io.micronaut.http.cookie.DefaultServerCookieEncoder

After, #10435, which was part of Micronaut Core 4.1.13 the ServerCookieEncoder, the application was using io.micronaut.http.netty.cookies.NettyServerCookieEncoder. I am still investigating why NettyServerCookieEnconder fails to encode my session cookie.

Thus, I am might be seeing a completely different issues as @loicmathieu

loicmathieu commented 1 month ago

The last comment of @sdelamo is interesting, we had to switch to the ServerCookieDecoder implementation due to a bug in Micronaut 4 so we configured it to io.micronaut.http.netty.cookies.NettyLaxServerCookieDecoder.

As far as I remember the bug in the default ServerCookieDecoder was parsing issue with domain cookie, using the io.micronaut.http.netty.cookies.NettyLaxServerCookieDecoder fixes the issue.

loicmathieu commented 1 month ago

The reference issue we had we cookie parsing, it may be related to this issue: https://github.com/micronaut-projects/micronaut-core/issues/10435

sdelamo commented 1 month ago

The last comment of @sdelamo is interesting, we had to switch to the ServerCookieDecoder implementation due to a bug in Micronaut 4 so we configured it to io.micronaut.http.netty.cookies.NettyLaxServerCookieDecoder.

Please, note that should not be longer necessary due to https://github.com/micronaut-projects/micronaut-core/pull/10659 if you are using core 4.3.13 or above.

As far as I remember the bug in the default ServerCookieDecoder was parsing issue with domain cookie, using the io.micronaut.http.netty.cookies.NettyLaxServerCookieDecoder fixes the issue.

I get the issue only when deploying to Amazon Elastic Beanstalk instead of locally. Thus, I think the cookie's domain part may be related, but I still need to get to the bottom of it.

tchiotludo commented 1 month ago

Just to confirm that I've tried with

All failed at approximately 5% of the request, only the 4.3.4 fixed the issue. My feeling is the security-module is the main reason, but not sure.

loicmathieu commented 1 month ago

The issue may be in Micronaut Security, there was changes in the JWT token validator and we do use JWT tokens: https://github.com/micronaut-projects/micronaut-security/compare/v4.6.6...v4.6.9

loicmathieu commented 1 month ago

Don't know if it helps but we have the following cookie after login image

loicmathieu commented 1 month ago

We reproduce the issue on one of our preview environment with only the JWT cookie, no other cookie exists. As it's a preview env we can experiment if someone has an idea to dig deeper.

graemerocher commented 1 month ago

could you try with a newer version of Micronaut but forcing an older version of Micronaut Security. If we can narrow it down there we at least know where to look.

loicmathieu commented 1 month ago

We're testing a bunch of things at the moment; we have a filter that executes before the security filter; if we move it after, it no longer fails.

We also plan to do exactly what you asking for, I'll keep you posted.

tchiotludo commented 1 month ago

@graemerocher, the first version of micronaut-security failing is the 4.6.8 I've update to micronaut-platform at 4.3.8, downgrading to micronaut-security to 4.6.7 at working well, starting from 4.6.8 I start to see errors. To have error, you need to siege any authenticated endpoint, since it's happen approximately 5%. First felling, this commit could be the reason: https://github.com/micronaut-projects/micronaut-security/commit/afd2f692a7cb2cb215f40e4e827ea4de91227139 (since we are using the JWT implementation)

graemerocher commented 1 month ago

thanks for the feedback that helps a great deal

tchiotludo commented 1 month ago

Latest test before weekend: I use micronaut core at 4.3.8, I use this version and replaced the bean from micronaut-security 4.6.10 (see here) and no more error on local.
Will validate after build on our preview env.

graemerocher commented 1 month ago

we can't see that commit

tchiotludo commented 1 month ago

It's just this file with a @Replaces to overload the micronaut bean from latest version

sdelamo commented 1 month ago

It's just this file with a @Replaces to overload the micronaut bean from latest version

Thanks @tchiotludo I will try to revert the changes we did lately to that file tomorrow and see if it helps.

sdelamo commented 1 month ago

@tchiotludo just to confirm:

If you set, it works ✅:

public class JwtTokenValidator<T> implements TokenValidator<T> {
...
   public Publisher<Authentication> validateToken(String token, @Nullable T request) {
        return validator.validate(token, request)
                .flatMap(jwtAuthenticationFactory::createAuthentication)
                .map(Flux::just)
                .orElse(Flux.empty());
    }
}
}

if you set, as we do currently it fails ❌:

public class JwtTokenValidator<T> implements TokenValidator<T> {
...
   public Publisher<Authentication> validateToken(String token, @Nullable T request) {
return Mono.fromCallable(() -> validator.validate(token, request))
            .flatMap(tokenOptional -> tokenOptional.flatMap(jwtAuthenticationFactory::createAuthentication)
                .map(Mono::just).orElse(Mono.empty())).subscribeOn(scheduler);
}
}

Is that so?

Aside, Do you have io.micrometer:context-propagation in your classpath for Reactor context propagation?

tchiotludo commented 1 month ago
loicmathieu commented 1 month ago

If I analyze our Gradle dependency graph, context-propagation is a transitive dependency of micronaut-runtime and micronaut-http, so even if we didn't have a direct reference to it, it should be included.

graemerocher commented 1 month ago

are you sure you are not confusing micronaut-context-propagation which is transitive of micronaut-runtime with io.micrometer:context-propagation?

loicmathieu commented 1 month ago

@graemerocher you're right, I read too quickly. So we're not using it, I confirm.

sdelamo commented 4 weeks ago

I think the longer term solution is https://github.com/micronaut-projects/micronaut-security/pull/1693