Feign OKHttpClient with Http2 - okhttp

I set up a basic http2 client with okhttp
http2 is enabled for the URL below.
#FeignClient(name = "client", url = "https://http2.pro/api/v1", configuration = FeignConfig.class)
public interface TestClient {
#GetMapping("/")
String callServer();
}
feign.client.config.default.logger-level=full
feign.okhttp.enabled=true
logging.level.com.example=DEBUG
logging.level.okhttp3=DEBUG
When I hit this service in the logs I see HTTP 1.1.
Since I already know this service is using Http2
How can i enforce my client to use HTTP 2 protocol.
om.example.http2clientpoc.TestClient : [TestClient#callServer] ---> GET https://http2.pro/api/v1/ HTTP/1.1
2023-02-07 12:42:53.704 DEBUG 53855 --- [ main] com.example.http2clientpoc.TestClient : [TestClient#callServer] ---> END HTTP (0-byte body)
2023-02-07 12:42:54.080 DEBUG 53855 --- [ main] okhttp3.internal.http2.Http2 : >> CONNECTION 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
2023-02-07 12:42:54.081 DEBUG 53855 --- [ main] okhttp3.internal.http2.Http2 : >> 0x00000000 6 SETTINGS
2023-02-07 12:42:54.081 DEBUG 53855 --- [ main] okhttp3.internal.http2.Http2 : >> 0x00000000 4 WINDOW_UPDATE
Prior knowledge is not working (exception message : H2_PRIOR_KNOWLEDGE cannot be used with HTTPS)
#Bean
OkHttpClient httpClient() {
return new OkHttpClient(new okhttp3.OkHttpClient.Builder()
.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)).build());
}

Try this:
#Bean
OkHttpClient httpClient() {
return new OkHttpClient(new okhttp3.OkHttpClient.Builder()
.protocols(List.of(Protocol.H2)).build());
}

Related

CSRF on spring cloud gateway removing formData from POST requests 400 bad request error

I have enabled CSRF on my spring cloud api gateway server.
I have angular as my GUI framework which calls the rest services through the api gateway.
I have used a custom filter to add the CSRF token to the response headers.
When the POST call is made I see that the formData is lost. So I always get 400 Bad request errors.
I disabled CSRF and the request goes through fine without any issues.
Is there something wrong?
Below is my spring cloud gateway configuration. Gateway is used only for routing the requests to other microservices, it does not have any controllers or rest endpoints.
#SpringBootApplication
public class GatewayApplication {
#Autowired
ProfileManager profileManager;
#PostConstruct
public void onInit() {
profileManager.printActiveProfiles();
}
public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); }
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
}
below is the filter code
#Component
public class CsrfHeaderFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Mono<CsrfToken> token = (Mono<CsrfToken>) exchange.getAttributes().get(CsrfToken.class.getName());
if (token != null) {
return token.flatMap(t -> chain.filter(exchange));
}
return chain.filter(exchange);
}
}
My POST rest endpoints are defined with
#RequestParam
below is the code from one of the rest service endpoints. It is an upstream service implemented using the traditional servlet springboot framework.
#RequestMapping(value = "terminate/{listName}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED)
#CrossOrigin
#Loggable (activityname = ActivityLogConstants.DESCRIPTOR_TERMINATE)
public Response terminate(#Context HttpServletRequest reqContext, #PathVariable String listName, #RequestParam(value = "rowData") String rowData)
throws ServiceException {....}
The formData is lost by the time the request reaches the upstream services.
Looks like the filter in spring cloud gateways is blocking formData
here is my netty configuration:
#Configuration
public class NettyConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Value("${server.max-initial-line-length:65536}")
private int maxInitialLingLength;
#Value("${server.max-http-header-size:65536}")
private int maxHttpHeaderSize;
public void customize(NettyReactiveWebServerFactory container) {
container.addServerCustomizers(
httpServer -> httpServer.httpRequestDecoder(
httpRequestDecoderSpec -> {
httpRequestDecoderSpec.maxHeaderSize(maxHttpHeaderSize);
httpRequestDecoderSpec.maxInitialLineLength(maxInitialLingLength);
return httpRequestDecoderSpec;
}
)
);
}
}
below is my application.yml
sample log:
2022-07-28 09:18:20.743 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.client.HttpClientOperations : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] Received response (auto-read:false) : [X-Content-Type-Options=nosniff, X-XSS-Protection=1; mode=block, Cache-Control=no-cache, no-store, max-age=0, must-revalidate, Pragma=no-cache, Expires=0, Strict-Transport-Security=max-age=31536000 ; includeSubDomains, X-Frame-Options=DENY, X-Application-Context=application:18080, Date=Thu, 28 Jul 2022 03:48:20 GMT, Connection=close, content-length=0]
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [response_received])
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] reactor.netty.channel.FluxReceive : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] FluxReceive{pending=0, cancelled=false, inboundDone=false, inboundError=null}: subscribing inbound receiver
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.client.HttpClientOperations : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] Received last HTTP packet
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Decreasing pending responses, now 0
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Last HTTP packet was sent, terminating the channel
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Completed 400 BAD_REQUEST
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Last HTTP response frame
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] c.m.webgateway.handler.RequestLogger : Total time required to process /cms-service/webapi/terminate/descriptor request 60055
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [response_completed])
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [disconnecting])
2022-07-28 09:18:20.752 DEBUG 26532 --- [ctor-http-nio-5] r.n.resources.PooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 ! R:localhost/127.0.0.1:18080] Channel closed, now: 0 active connections, 4 inactive connections and 0 pending acquire requests.
2022-07-28 09:18:20.752 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 ! R:localhost/127.0.0.1:18080] onStateChange(PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 ! R:localhost/127.0.0.1:18080]}, [disconnecting])
2022-07-28 09:18:23.805 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Increasing pending responses, now 1
2022-07-28 09:18:23.805 DEBUG 26532 --- [ctor-http-nio-5] reactor.netty.http.server.HttpServer : [id:b0f975eb-12, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Handler is being applied: org.springframework.http.server.reactive.ReactorHttpHandlerAdapter#7c82616c
2022-07-28 09:18:23.805 DEBUG 26532 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [b0f975eb-12, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] HTTP GET "/cms-service/webapi/data/descriptor"
below is the link to the sample project.
https://github.com/manjosh1990/webgateway-issues
I tried to ignore FORM URL ENCODED requests and GET request, but it still does not work
private static final Set<HttpMethod> ALLOWED_METHODS = new HashSet<>(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll().and()
.csrf(csrf -> csrf
.requireCsrfProtectionMatcher(ignoringFormUrlEncodedContentType())
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()));
return http.build();
}
private ServerWebExchangeMatcher ignoringFormUrlEncodedContentType() {
return (exchange) -> !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(
exchange.getRequest().getHeaders().getContentType()) || !ALLOWED_METHODS.contains(exchange.getRequest().getMethod())
? ServerWebExchangeMatcher.MatchResult.match()
: ServerWebExchangeMatcher.MatchResult.notMatch();
}
Thanks for the minimal sample to reproduce the issue!
After some testing, I'm unable to come up with a workaround or fix for your configuration that allows a form post (URL-encoded) to pass through the gateway with CSRF protection enabled. My best guess is it has to do with how Spring Security is consuming the request body (which should be cached for subsequent filters to consume) vs how Spring Cloud Gateway is consuming the request body in order to proxy to the downstream service.
I tested this by disabling CSRF protection and adding the following filter:
#Component
public class TestWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() -> exchange.getFormData()
.doOnSuccess(System.out::println))
.then(chain.filter(exchange));
}
}
In my testing, this causes the request through the gateway to block for a long time before receiving:
{
"timestamp": "2022-08-10T19:13:54.265+00:00",
"status": 400,
"error": "Bad Request",
"path": "/cms-service/webapi/service/post/test"
}
Since this appears to be a bug in Spring Security, I'd recommend submitting a bug in Spring Security and we can work through it from there.
If you would like to work around the issue in the meantime, you can disable CSRF protection for these types of requests, as follows:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.csrf((csrf) -> csrf
.requireCsrfProtectionMatcher(ignoringFormUrlEncodedContentType())
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
)
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt);
return http.build();
}
private ServerWebExchangeMatcher ignoringFormUrlEncodedContentType() {
return (exchange) -> !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(
exchange.getRequest().getHeaders().getContentType())
? ServerWebExchangeMatcher.MatchResult.match()
: ServerWebExchangeMatcher.MatchResult.notMatch();
}
}
Important: This is not ideal, because these requests won't be protected. However, this might make sense if these requests were never performed in a browser. In that case, it would make sense to have a separate authentication mechanism, such as requiring a bearer token instead of form login, etc. (as in the example above).

All endpoints return 401/403 in ControllerTest when adding resourceserver in my Spring Boot project

In these days, we are going to migrate our custom JWT filter to Auth0 IDP.
I was using Spring 2.7.1, WebFlux/Kotlin Coroutines in this project. I created a sample project to demo Reactive Spring Security OAuth2 with Auth0 IDP issue.
BTW, I've maintained a WebMvc version to demo Spring Security OAuth2 and OAuth0(updated to Spring 2.7.1), in which the tests are working well.
When I added org.springframework.boot:spring-boot-starter-oauth2-resource-server to our project deps to enable OAuth2 resource server support in our backend API.
But now all API endpoints are protected and returns 401 or 403.
My security config like this.
#Bean
fun springWebFilterChain(http: ServerHttpSecurity, reactiveJwtDecoder: ReactiveJwtDecoder): SecurityWebFilterChain =
http {
csrf { disable() }
httpBasic { disable() }
formLogin { disable() }
logout { disable() }
// enable OAuth2 resource server support
oauth2ResourceServer { jwt { jwtDecoder = reactiveJwtDecoder } }
exceptionHandling {
authenticationEntryPoint = problemSupport
accessDeniedHandler = problemSupport
}
authorizeExchange {
authorize(pathMatchers(GET, "/v1/me"), authenticated)
authorize(anyExchange, permitAll)
}
}
The testing codes is like this, check the complete codes(The tests are ported from other projects, I have not added jwt mock).
#Test
fun `get all posts`() = runTest {
coEvery { posts.findAll() } returns flowOf(
Post(
id = UUID.randomUUID(),
title = "test title",
content = "test content"
)
)
client.get()
.uri("/posts").accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk
.expectBodyList(Post::class.java).hasSize(1)
coVerify(exactly = 1) { posts.findAll() }
}
The get all posts endpoints is set to permitAll, when running curl http://localhost:8080/posts command in a opening Powershell, it works well and print all posts in the console.
When running test of GET /posts endpoint. I got the following info from console.
2022-07-01 12:45:27.021 DEBUG 14132 --- [ main] o.s.w.r.f.client.ExchangeFunctions : [6144e499] HTTP GET /posts
2022-07-01 12:45:27.069 DEBUG 14132 --- [ parallel-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [26193fe] HTTP GET "/posts"
2022-07-01 12:45:27.108 DEBUG 14132 --- [ parallel-1] .s.s.w.s.u.m.AndServerWebExchangeMatcher : Trying to match using org.springframework.security.web.server.csrf.CsrfWebFilter$DefaultRequireCsrfProtectionMatcher#6fc32403
2022-07-01 12:45:27.110 DEBUG 14132 --- [ parallel-1] .s.s.w.s.u.m.AndServerWebExchangeMatcher : Did not match
2022-07-01 12:45:27.146 DEBUG 14132 --- [ parallel-2] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
2022-07-01 12:45:27.151 DEBUG 14132 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=POST}
2022-07-01 12:45:27.151 DEBUG 14132 --- [ parallel-2] athPatternParserServerWebExchangeMatcher : Request 'GET /posts' doesn't match 'POST /logout'
2022-07-01 12:45:27.152 DEBUG 14132 --- [ parallel-2] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
2022-07-01 12:45:27.156 DEBUG 14132 --- [ parallel-2] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/posts' using org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager#66bdd968
2022-07-01 12:45:27.159 DEBUG 14132 --- [ parallel-2] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession#529dcf30'
2022-07-01 12:45:27.160 DEBUG 14132 --- [ parallel-2] o.s.s.w.s.a.AuthorizationWebFilter : Authorization failed: Access Denied
2022-07-01 12:45:27.174 DEBUG 14132 --- [ parallel-2] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession#529dcf30'
2022-07-01 12:45:27.184 DEBUG 14132 --- [ parallel-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [26193fe] Completed 401 UNAUTHORIZED
2022-07-01 12:45:27.189 DEBUG 14132 --- [ parallel-2] o.s.w.r.f.client.ExchangeFunctions : [6144e499] [609b7bfb] Response 401 UNAUTHORIZED
2022-07-01 12:45:27.207 ERROR 14132 --- [ main] o.s.t.w.reactive.server.ExchangeResult : Request details for assertion failure:
> GET /posts
> WebTestClient-Request-Id: [1]
> Accept: [application/json]
No content
< 401 UNAUTHORIZED Unauthorized
< WWW-Authenticate: [Bearer]
< Cache-Control: [no-cache, no-store, max-age=0, must-revalidate]
< Pragma: [no-cache]
< Expires: [0]
< X-Content-Type-Options: [nosniff]
< X-Frame-Options: [DENY]
< X-XSS-Protection: [1 ; mode=block]
< Referrer-Policy: [no-referrer]
0 bytes of content (unknown content-type).
java.lang.AssertionError: Status expected:<200 OK> but was:<401 UNAUTHORIZED>
Expected :200 OK
Actual :401 UNAUTHORIZED
<Click to see difference>
I have set logging level to TRACE, but there is no log to check our pathmatcher rules. It seems it does not check the pathmatchers in the authorizeRequest.
I have set disabled to crsf, but crsf filter still participate in the security check progress, it also tired to match the /logout url.
#WebfluxTest / #WebmvcTest load very little conf. I do import my web-security cong like you do in your answer.
You might whish to unit test the production security rules for the controller endpoint. If so, import regular security conf (not a permitAll() test conf) and configure security context with either:
jwt() WebTestClient mutator / WebMvc post-processor from spring-security-test
#WithMockJwtAuth from spring-addons
It seems the WebFluxTest annotated tests did not scan my custom SecurityConfig( which is a simple config NOT extends the XXXSecurityConfigurerAdapater) in the project, but use the Spring Boot built-in default security config instead.
Currently I have to add my SecurityConfig explicitly to enable it in the tests.
class TestsClass{
#TestConfiguration
#Import(SecuirtyConfig::class)
class TestConfig
//test methods
}
I am not sure it is the original design purpose or other reason.

Why does Spring Security reject my Keycloak auth token with "No AuthenticationProvider found"?

I'm trying to figure out why my Spring Boot application is rejecting my Keycloak JWT bearer token with a "No AuthenticationProvider found" error message.
I have a few services running in a docker compose environment:
ui (angular) -> proxy (nginx) -> rest api (spring boot) -> auth service (keycloak)
The angular ui pulls the correct keycloak client from the rest service, and then authenticates without issue. I get back a JWT token, and then turn around and hand that to follow on requests to the rest api in a header Authorization: bearer [token].
In the rest API, I can see the correct bearer token come in as a header:
2022-02-11 01:01:31.411 DEBUG 13 --- [nio-8080-exec-4] o.a.coyote.http11.Http11InputBuffer : Received [GET /api/v3/accounts HTTP/1.0
X-Real-IP: 192.168.80.1
X-Forwarded-For: 192.168.80.1
Host: rest-api.mylocal.com
Connection: close
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciO...
...
2022-02-11 01:01:31.421 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.PreAuthActionsHandler : adminRequest http://rest-api.mylocal.com/api/v3/accounts
...
So the bearer token is there, and with https://jwt.io/ I can verify it's what I would expect:
{
"exp": 1644515847,
...
"iss": "http://auth-service.mylocal.com/auth/realms/LocalTestRealm",
...
"typ": "Bearer",
"azp": "LocalTestClient",
...
"allowed-origins": [
"http://web-ui.mylocal.com"
],
"realm_access": {
"roles": [
"offline_access",
"default-roles-localtestrealm",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email profile",
...
}
Processing continues by the rest api - it contacts the keycloak service and pulls the well known config:
...
2022-02-11 01:01:33.321 INFO 13 --- [nio-8080-exec-4] o.keycloak.adapters.KeycloakDeployment : Loaded URLs from http://auth-service.mylocal.com/auth/realms/LocalTestRealm/.well-known/openid-configuration
...
Finally it looks like it successfully parses the bearer token apart, grabs the user and authenticates them:
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Releasing connection [{}->http://auth-service.mylocal.com:80][null]
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Pooling connection [{}->http://auth-service.mylocal.com:80][null]; keep alive indefinitely
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Notifying no-one, there are no waiting threads
2022-02-11 01:01:33.530 DEBUG 13 --- [nio-8080-exec-4] o.k.a.rotation.JWKPublicKeyLocator : Realm public keys successfully retrieved for client LocalTestClient. New kids: [8a7dIQFASdC8BHa0mUWwZX7RBBJSeJItdmzah0Ybpcw]
2022-02-11 01:01:33.546 DEBUG 13 --- [nio-8080-exec-4] o.k.a.BearerTokenRequestAuthenticator : successful authorized
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] o.k.a.RefreshableKeycloakSecurityContext : checking whether to refresh.
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] org.keycloak.adapters.AdapterUtils : useResourceRoleMappings
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] org.keycloak.adapters.AdapterUtils : Setting roles:
2022-02-11 01:01:33.555 DEBUG 13 --- [nio-8080-exec-4] a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: []
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.RequestAuthenticator : User 'bf7307ca-9352-4a02-b288-0565e2b57292' invoking 'http://rest-api.mylocal.com/api/v3/accounts' on client 'LocalTestClient'
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.RequestAuthenticator : Bearer AUTHENTICATED
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
and then immediately after that fails with the No AuthenticationProvider found error:
2022-02-11 01:01:33.559 TRACE 13 --- [nio-8080-exec-4] f.KeycloakAuthenticationProcessingFilter : Failed to process authentication request
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:234) ~[spring-security-core-5.5.1.jar!/:5.5.1]
I'm at a loss how it can say Bearer AUTHENTICATED followed by Auth outcome: AUTHENTICATED followed by No AuthenticationProvider found... I'm assuming it somehow can't convert this bearer token into a Keycloak token, even though it definitely came from my Keycloak server.
My app config:
#ComponentScan({"com.mycompany"})
#Configuration
#EnableJpaRepositories(basePackages = "com.mycompany")
#EntityScan("com.mycompany")
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class ApplicationConfiguration
extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
// These paths (comma separated) are allowed to all
.antMatchers("/api/v3/auth/config").permitAll()
.and()
.authorizeRequests()
// Everything else should be authenticated
.anyRequest().authenticated()
.and()
.csrf().disable();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
// This just pulls the Keycloak config from a DB instead of the config file
return new CustomKeycloakConfigResolver();
// return new KeycloakSpringBootConfigResolver();
}
}
Missing the global config to autowire in a Keycloak auth provider:
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth)
throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider =
keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper()
);
auth.authenticationProvider(keycloakAuthenticationProvider);
}

Trace Id not getting sent correctly between spring boot apps

I have a spring boot console app that sends a http request to a spring boot api, the trace id looks correct in the console app, but is a completely different trace id in the api side. Why would this happen? Do I need to inject something into the trace context in the console app? Thanks for any help!
Console app:
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
logger.info("right before setting header");
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
header.add("X-B3-TraceId", "b3c10720b744fa9d");
header.add("X-B3-SpanId", "b3c10720b744fa9d");
logger.info("right before rest template");
restTemplate
.exchange(builder.build(false).toUriString(), HttpMethod.POST,
new HttpEntity<>("some request body", header), String.class);
logger.info("after rest template");
console app log(b3c10720b744fa9d is expected trace id):
2020-11-13T18:49:41.686 INFO [-,b3c10720b744fa9d,,] 16268 --- [ main] c.m.f.s.common.impl.TrackingServiceImpl : right before setting header
2020-11-13T18:49:41.687 INFO [-,b3c10720b744fa9d,,] 16268 --- [ main] c.m.f.s.common.impl.TrackingServiceImpl : right before rest template
api:
#PostMapping("/trace")
public #ResponseBody ResponseEntity postTracking(
#RequestBody String jsonRequest, HttpServletRequest httpRequest) {
List<String> headers = Collections.list(httpRequest.getHeaderNames());
headers.forEach(header -> logger.info("Header {} Value {}", header, httpRequest.getHeader(header)));
api logs (now showing 221cd9ce3908aa1a as trace id):
2020-11-15 17:19:12.814 INFO [-,221cd9ce3908aa1a,221cd9ce3908aa1a,false] 22372 --- [nio-8081-exec-1] c.m.f.t.controller.TrackingController : Header accept Value text/plain, application/json, application/cbor, application/*+json,
2020-11-15 17:19:12.815 INFO [-,221cd9ce3908aa1a,221cd9ce3908aa1a,false] 22372 --- [nio-8081-exec-1] c.m.f.t.controller.TrackingController : Header content-type Value text/plain;charset=UTF-8
2020-11-15 17:19:12.815 INFO [-,221cd9ce3908aa1a,221cd9ce3908aa1a,false] 22372 --- [nio-8081-exec-1] c.m.f.t.controller.TrackingController : Header x-b3-traceid Value 221cd9ce3908aa1a
2020-11-15 17:19:12.815 INFO [-,221cd9ce3908aa1a,221cd9ce3908aa1a,false] 22372 --- [nio-8081-exec-1] c.m.f.t.controller.TrackingController : Header x-b3-spanid Value 221cd9ce3908aa1a
2020-11-15 17:19:12.815 INFO [-,221cd9ce3908aa1a,221cd9ce3908aa1a,false] 22372 --- [nio-8081-exec-1] c.m.f.t.controller.TrackingController : Header x-b3-sampled Value 0

Spring Resttemplate with BasicCredentialsProvider return 401 unathorized

Problem:
I try to make rest call using Resttemplate but it gives 401 status code after that retries once again and gives 200 status code. So every time it makes two calls.
So My question is why Resttemplate doesn't use BasicCredentialsProvider in a first call?
Below is my RestTemplate configuration.
#Bean
public CloseableHttpClient httpClient() {
try {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("XXXX", "XXXXX"));
CloseableHttpClient httpclient = HttpClientBuilder
.create()
.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultHeaders(headers)
.build();
return httpclient;
}
catch (Exception e) {
throw e;
}
}
#Bean
public RestTemplate restTemplate(){
RestTemplate template = new RestTemplate();
HttpComponentsClientHttpRequestFactory requestFactory = new
HttpComponentsClientHttpRequestFactory(httpClient());
template.setRequestFactory(requestFactory);
return template ;
}
RestTemplate Call
template.getForObject(...;
So here am I missing something which is not taking credential in the first call?
Below are the httpclient related logs
2017-04-26 16:28:36.698 DEBUG 6100 --- [nio-8080-exec-1] org.apache.http.headers : http-outgoing-0 << HTTP/1.1 401 Unauthorized
2017-04-26 16:28:36.703 DEBUG 6100 --- [nio-8080-exec-1] o.a.h.i.c.TargetAuthenticationStrategy : Challenge for Negotiate authentication scheme not available
2017-04-26 16:28:36.703 DEBUG 6100 --- [nio-8080-exec-1] o.a.h.i.c.TargetAuthenticationStrategy : Challenge for Kerberos authentication scheme not available
2017-04-26 16:28:36.703 DEBUG 6100 --- [nio-8080-exec-1] o.a.h.i.c.TargetAuthenticationStrategy : Challenge for NTLM authentication scheme not available
2017-04-26 16:28:36.703 DEBUG 6100 --- [nio-8080-exec-1] o.a.h.i.c.TargetAuthenticationStrategy : Challenge for Digest authentication scheme not available
2017-04-26 16:28:36.706 DEBUG 6100 --- [nio-8080-exec-1] o.a.http.impl.auth.HttpAuthenticator : Selected authentication options: [BASIC [complete=true]]
2017-04-26 16:28:36.706 DEBUG 6100 --- [nio-8080-exec-1] o.a.http.impl.execchain.MainClientExec : Target auth state: CHALLENGED
2017-04-26 16:28:36.706 DEBUG 6100 --- [nio-8080-exec-1] o.a.http.impl.auth.HttpAuthenticator : Generating response to an authentication challenge using basic scheme
2017-04-26 16:28:36.709 DEBUG 6100 --- [nio-8080-exec-1] o.a.http.impl.execchain.MainClientExec : Proxy auth state: UNCHALLENGED
I had made the same call with HTTPClient directly and it doesn't have any problem.
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet getRequest = new HttpGet(url);
getRequest.addHeader("content-type", "application/vnd.nativ.mio.v1+json");
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("XXXXX", "XXXX"));
httpClient.setCredentialsProvider(credentialsProvider);
HttpResponse response = httpClient.execute(getRequest);

Resources