Angular v. v4.0.2
Spring Boot v. 1.5.2.RELEASE
Keycloak v.2.4.0.Final (will upgrade later)
I read this mail converstion about the same problem: http://keycloak-user.88327.x6.nabble.com/keycloak-user-NOT-ATTEMPTED-bearer-only-error-while-trying-to-access-server-from-client-td927.html and this http://slackspace.de/articles/authentication-with-spring-boot-angularjs-and-keycloak/
I use the following http service for making authorized requests:
#Injectable()
export class AuthHttpService extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) {
super(backend, defaultOptions);
}
private setToken(options: RequestOptionsArgs) {
if (options == null || AuthService.auth == null || AuthService.auth.authz == null || AuthService.auth.authz.token == null) {
console.log("Need a token, but no token is available, not setting bearer token.");
return;
}
console.log(AuthService.auth.authz.token);
options.headers.set('Authorization', 'Bearer ' + AuthService.auth.authz.token);
}
private configureRequest(f:Function, url:string | Request, options:RequestOptionsArgs, body?: any):Observable<Response> {
let tokenPromise:Promise<string> = this.authService.getToken();
let tokenObservable:Observable<string> = Observable.fromPromise(tokenPromise);
let tokenUpdateObservable:Observable<any> = Observable.create((observer) => {
if (options == null) {
let headers = new Headers();
options = new RequestOptions({ headers: headers });
}
this.setToken(options);
observer.next();
observer.complete();
});
let requestObservable:Observable<Response> = Observable.create((observer) => {
let result;
if (body) {
result = f.apply(this, [url, body, options]);
} else {
result = f.apply(this, [url, options]);
}
result.subscribe((response) => {
observer.next(response);
observer.complete();
}, (err) => observer.error(err));
});
return <Observable<Response>>Observable
.merge(tokenObservable, tokenUpdateObservable, requestObservable, 1)
.filter((response) => response instanceof Response);
}
...
Application.properties
The token is correctly logged.
server.port = 8081
keycloak.realm = apprealm
keycloak.auth-server-url = http://localhost:8080/auth
keycloak.ssl-required = external
keycloak.resource = appbackend
keycloak.bearer-only = true
keycloak.credentials.secret = ...
keycloak.securityConstraints[0].securityCollections[0].name = secure
keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=frontenduser
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /r/s/*
logging.level.org.keycloak=DEBUG
The user which I use in the frontend has that role.
Error in backend
2017-04-22 15:40:00.517 DEBUG 14088 --- [nio-8081-exec-1] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8081/r/s/e/p/m
2017-04-22 15:40:00.540 DEBUG 14088 --- [nio-8081-exec-1] o.k.a.a.ClientCredentialsProviderUtils : Using provider 'secret' for authentication of client 'appbackend'
2017-04-22 15:40:00.543 DEBUG 14088 --- [nio-8081-exec-1] o.k.a.a.ClientCredentialsProviderUtils : Loaded clientCredentialsProvider secret
2017-04-22 15:40:00.545 DEBUG 14088 --- [nio-8081-exec-1] o.k.a.a.ClientCredentialsProviderUtils : Loaded clientCredentialsProvider jwt
2017-04-22 15:40:00.552 DEBUG 14088 --- [nio-8081-exec-1] o.k.a.a.ClientCredentialsProviderUtils : Loaded clientCredentialsProvider secret
2017-04-22 15:40:00.553 DEBUG 14088 --- [nio-8081-exec-1] o.k.a.a.ClientCredentialsProviderUtils : Loaded clientCredentialsProvider jwt
2017-04-22 15:40:00.625 DEBUG 14088 --- [nio-8081-exec-1] o.keycloak.adapters.KeycloakDeployment : resolveUrls
2017-04-22 15:40:00.631 DEBUG 14088 --- [nio-8081-exec-1] o.k.adapters.KeycloakDeploymentBuilder : Use authServerUrl: http://localhost:8080/auth, tokenUrl: http://localhost:8080/auth/realms/apprealm/protocol/openid-connect/token, relativeUrls: NEVER
2017-04-22 15:40:00.662 DEBUG 14088 --- [nio-8081-exec-1] o.k.adapters.RequestAuthenticator : NOT_ATTEMPTED: bearer only
2017-04-22 15:40:00.681 INFO 14088 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-04-22 15:40:00.681 INFO 14088 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-04-22 15:40:00.723 INFO 14088 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 42 ms
2017-04-22 15:40:08.560 DEBUG 14088 --- [nio-8081-exec-2] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8081/r/s/e/p/m
2017-04-22 15:40:08.560 DEBUG 14088 --- [nio-8081-exec-2] o.k.adapters.RequestAuthenticator : NOT_ATTEMPTED: bearer only
Edit http
HTTP/1.1 401
Cache-Control: private
Expires: Thu, 01 Jan 1970 01:00:00 CET
WWW-Authenticate: Bearer realm="apprealm"
Access-Control-Allow-Origin: http://localhost:4200
Vary: Origin
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1800
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Content-Length: 0
Date: Sun, 23 Apr 2017 17:04:07 GMT
Edit 2: http raw request
OPTIONS http://localhost:8081/r/p/main HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Access-Control-Request-Method: PUT
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Access-Control-Request-Headers: authorization,content-type
Accept: */*
Referer: http://localhost:4200/b
Accept-Encoding: gzip, deflate, sdch
Accept-Language: nl-NL,nl;q=0.8,en-US;q=0.6,en;q=0.4
What could be the problem?
I've just had this error and it was because the Authorization header is missing the text "bearer " before the actual token.
Related
Version
Spring Boot Version: 2.6.5
Spring Cloud Version: 2021.0.0
The application built has the following characteristics:
It is a reactive application (Reactor + WebFlux)
It is an Oauth2 Client Application that uses Grant Type = Authorization Code.
It uses Spring Cloud Gateway to proxy Oauth2 Resources.
It uses Spring Session (Redis) to be able to accomplish transparent horizontal scaling.
Problem:
Requesting a resource that goes through the Spring Cloud Gateway to a downstream service returns a correct 200, but after the application is restarted while there is an active session saved in Redis it will return a 302. I find this to be a huge problem because it basically prevents horizontal scaling, which is my whole reason to implement Spring Session in Redis.
Steps to reproduce:
Start Application.
User requests loading application in the browser: https://localhost:9093/hello.html
Session is successfully created in Redis.
User redirected to the Oauth2 Server for Authentication
User Authenticates
User redirected to application after successful login.
Application loads in browser without a problem.
User requests a gateway resource: https://localhost:9093/entries
Resources loads fine with a 200.
Everything great so far.
Restart the server.
(Session still active, saved on Redis)
User requests the gateway resource (https://localhost:9093/entries).
Resources returns 302 and redirected to the Ouath Server (BAD)
Logs
The difference in logs when the resource loads vs does not load is the following:
Correct Loading
2022-03-30 18:25:06.655 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [PathPatternParserServerWebExchangeMatcher PathPatternParserServerWebExchangeMatcher.java:95->matches] : Checking match of request : '/wasd/selfservice/api/ccbConfiguration'; against '/wasd/**'
2022-03-30 18:25:06.655 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [OrServerWebExchangeMatcher OrServerWebExchangeMatcher.java:60->lambda$matches$2] : matched
2022-03-30 18:25:06.655 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [DelegatingReactiveAuthorizationManager DelegatingReactiveAuthorizationManager.java:55->lambda$check$1] : Checking authorization on '/wasd/selfservice/api/ccbConfiguration' using org.springframework.security.config.web.server.ServerHttpSecurity$AuthorizeExchangeSpec$Access$$Lambda$1069/0x0000000800995840#7ffc1fdb
2022-03-30 18:25:06.656 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [AuthorizationWebFilter AuthorizationWebFilter.java:52->lambda$filter$2] : Authorization successful
2022-03-30 18:25:06.656 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [RoutePredicateHandlerMapping RoutePredicateHandlerMapping.java:142->lambda$lookupRoute$6] : Route matched: ssa-resources
2022-03-30 18:25:06.656 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [RoutePredicateHandlerMapping RoutePredicateHandlerMapping.java:90->lambda$getHandlerInternal$0] : Mapping [Exchange: GET https://localhost:9093/wasdservice/wasd/selfservice/api/ccbConfiguration] to Route{id='ssa-resources', uri=https://wasd-api-int.company.com:443, order=0, predicate=Paths: [/wasdservice/wasd/**], match trailing slash: true, gatewayFilters=[[gov.miamidade.wasd.client.gateway.StripBasePathGatewayFilterFactory$$Lambda$1563/0x0000000800b4f040#4d05f800, order = 1], [org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory$$Lambda$1564/0x0000000800b4f440#46c35be4, order = 2]], metadata={}}
2022-03-30 18:25:06.657 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [RoutePredicateHandlerMapping AbstractHandlerMapping.java:189->lambda$getHandler$1] : [cb1dc8d7-90] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler#546da583
2022-03-30 18:25:06.657 [lettuce-kqueueEventLoop-5-1] DEBUG 90299 --- [FilteringWebHandler FilteringWebHandler.java:85->handle] : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter#86e7c7a}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter#29617475}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter#271df4c0}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter#22edca96}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter#6e1bd2b}, order = 0], [gov.miamidade.wasd.client.gateway.StripBasePathGatewayFilterFactory$$Lambda$1563/0x0000000800b4f040#4d05f800, order = 1], [org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory$$Lambda$1564/0x0000000800b4f440#46c35be4, order = 2], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter#6e863469}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration$NoLoadBalancerClientFilter#18a66406}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter#591bff1d}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter#38379575}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter#6a9e88f1}, order = 2147483647]]
.]]]' in WebSession: 'org.springframework.session.web.server.session.SpringSessionWebSessionStore$SpringSessionWebSession#118f2254'
2022-03-30 18:25:06.658 [reactor-http-kqueue-2] DEBUG 90299 --- [PooledConnectionProvider Loggers.java:250->debug] : [1bea583a, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] Channel acquired, now: 1 active connections, 0 inactive connections and 0 pending acquire requests.
2022-03-30 18:25:06.659 [reactor-http-kqueue-2] DEBUG 90299 --- [HttpClientConnect Loggers.java:250->debug] : [1bea583a-5, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] Handler is being applied: {uri=https://wasd-api-int.company.com/wasd/selfservice/api/ccbConfiguration, method=GET}
2022-03-30 18:25:06.659 [reactor-http-kqueue-2] DEBUG 90299 --- [DefaultPooledConnectionProvider Loggers.java:250->debug] : [1bea583a-5, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] onStateChange(GET{uri=/wasd/selfservice/api/ccbConfiguration, connection=PooledConnection{channel=[id: 0x1bea583a, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443]}}, [request_prepared])
2022-03-30 18:25:06.659 [reactor-http-kqueue-4] DEBUG 90299 --- [FluxReceive Loggers.java:250->debug] : [cb1dc8d7-16, L:/0:0:0:0:0:0:0:1%0:9093 - R:/0:0:0:0:0:0:0:1%0:56346] FluxReceive{pending=0, cancelled=false, inboundDone=true, inboundError=null}: subscribing inbound receiver
2022-03-30 18:25:06.660 [reactor-http-kqueue-2] DEBUG 90299 --- [DefaultPooledConnectionProvider Loggers.java:250->debug] : [1bea583a-5, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] onStateChange(GET{uri=/wasd/selfservice/api/ccbConfiguration, connection=PooledConnection{channel=[id: 0x1bea583a, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443]}}, [request_sent])
2022-03-30 18:25:06.779 [reactor-http-kqueue-2] DEBUG 90299 --- [HttpClientOperations Loggers.java:250->debug] : [1bea583a-5, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] Received response (auto-read:false) : [Cache-Control=no-cache, no-store, max-age=0, must-revalidate, Pragma=no-cache, Transfer-Encoding=chunked, Content-Type=application/json, Expires=0, Vary=Access-Control-Request-Headers, Request-Context=appId=e6c5278f-0ac3-4701-9647-f98e97152b1f, X-Content-Type-Options=nosniff, X-XSS-Protection=1; mode=block, Strict-Transport-Security=max-age=31536000 ; includeSubDomains, X-Frame-Options=DENY, Set-Cookie=ARRAffinity=64fcec8b11e29841b77fe53d1af49439c9f64a9de8bd5c352f6db54bf06a60d1;Path=/;HttpOnly;Secure;Domain=wasd-api-int.company.com, Set-Cookie=ARRAffinitySameSite=64fcec8b11e29841b77fe53d1af49439c9f64a9de8bd5c352f6db54bf06a60d1;Path=/;HttpOnly;SameSite=None;Secure;Domain=wasd-api-int.company.com, Date=Wed, 30 Mar 2022 22:25:05 GMT]
2022-03-30 18:25:06.779 [reactor-http-kqueue-2] DEBUG 90299 --- [DefaultPooledConnectionProvider Loggers.java:250->debug] : [1bea583a-5, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] onStateChange(GET{uri=/wasd/selfservice/api/ccbConfiguration, connection=PooledConnection{channel=[id: 0x1bea583a, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443]}}, [response_received])
2022-03-30 18:25:06.780 [reactor-http-kqueue-2] DEBUG 90299 --- [FluxReceive Loggers.java:250->debug] : [1bea583a-5, L:/192.168.1.11:56359 - R:wasd-api-int.company.com/10.82.52.4:443] FluxReceive{pending=0, cancelled=false, inboundDone=false, inboundError=null}: subscribing inbound receiver
Incorrect Loading
2022-03-30 18:31:56.074 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [PathPatternParserServerWebExchangeMatcher PathPatternParserServerWebExchangeMatcher.java:95->matches] : Checking match of request : '/wasd/selfservice/api/ccbConfiguration'; against '/wasd/**'
2022-03-30 18:31:56.075 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [OrServerWebExchangeMatcher OrServerWebExchangeMatcher.java:60->lambda$matches$2] : matched
2022-03-30 18:31:56.075 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [DelegatingReactiveAuthorizationManager DelegatingReactiveAuthorizationManager.java:55->lambda$check$1] : Checking authorization on '/wasd/selfservice/api/ccbConfiguration' using org.springframework.security.config.web.server.ServerHttpSecurity$AuthorizeExchangeSpec$Access$$Lambda$1070/0x000000080099e840#1107fca4
2022-03-30 18:31:56.075 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [AuthorizationWebFilter AuthorizationWebFilter.java:52->lambda$filter$2] : Authorization successful
2022-03-30 18:31:56.105 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [RoutePredicateHandlerMapping RoutePredicateHandlerMapping.java:142->lambda$lookupRoute$6] : Route matched: ssa-resources
2022-03-30 18:31:56.105 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [RoutePredicateHandlerMapping RoutePredicateHandlerMapping.java:90->lambda$getHandlerInternal$0] : Mapping [Exchange: GET https://localhost:9093/wasdservice/wasd/selfservice/api/ccbConfiguration] to Route{id='ssa-resources', uri=https://wasd-api-int.company.com:443, order=0, predicate=Paths: [/wasdservice/wasd/**], match trailing slash: true, gatewayFilters=[[com.mycompany.wasd.client.gateway.StripBasePathGatewayFilterFactory$$Lambda$1543/0x0000000800b38440#2f286cb, order = 1], [org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory$$Lambda$1544/0x0000000800b38840#19401a72, order = 2]], metadata={}}
2022-03-30 18:31:56.106 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [RoutePredicateHandlerMapping AbstractHandlerMapping.java:189->lambda$getHandler$1] : [cc95c8f2-1] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler#addc61b
2022-03-30 18:31:56.107 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [FilteringWebHandler FilteringWebHandler.java:85->handle] : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter#142f0ac1}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter#11544ddd}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter#3f7d20c5}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter#f4a21cb}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter#456d3914}, order = 0], [com.mycompany.wasd.client.gateway.StripBasePathGatewayFilterFactory$$Lambda$1543/0x0000000800b38440#2f286cb, order = 1], [org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory$$Lambda$1544/0x0000000800b38840#19401a72, order = 2], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter#1847da64}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration$NoLoadBalancerClientFilter#26a0068a}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter#635f4be1}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter#27acd9a7}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter#5967492a}, order = 2147483647]]
.]]]' in WebSession: 'org.springframework.session.web.server.session.SpringSessionWebSessionStore$SpringSessionWebSession#3d103de5'
2022-03-30 18:31:56.144 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [AndServerWebExchangeMatcher AndServerWebExchangeMatcher.java:61->lambda$matches$0] : Trying to match using OrServerWebExchangeMatcher{matchers=[PathMatcherServerWebExchangeMatcher{pattern='/**', method=GET}]}
2022-03-30 18:31:56.144 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [OrServerWebExchangeMatcher OrServerWebExchangeMatcher.java:57->lambda$matches$0] : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/**', method=GET}
2022-03-30 18:31:56.145 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [PathPatternParserServerWebExchangeMatcher PathPatternParserServerWebExchangeMatcher.java:95->matches] : Checking match of request : '/wasd/selfservice/api/ccbConfiguration'; against '/**'
2022-03-30 18:31:56.145 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [OrServerWebExchangeMatcher OrServerWebExchangeMatcher.java:60->lambda$matches$2] : matched
2022-03-30 18:31:56.146 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [AndServerWebExchangeMatcher AndServerWebExchangeMatcher.java:61->lambda$matches$0] : Trying to match using NegatedServerWebExchangeMatcher{matcher=OrServerWebExchangeMatcher{matchers=[PathMatcherServerWebExchangeMatcher{pattern='/favicon.*', method=null}]}}
2022-03-30 18:31:56.147 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [OrServerWebExchangeMatcher OrServerWebExchangeMatcher.java:57->lambda$matches$0] : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/favicon.*', method=null}
2022-03-30 18:31:56.148 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [PathPatternParserServerWebExchangeMatcher PathPatternParserServerWebExchangeMatcher.java:87->lambda$matches$1] : Request 'GET /wasd/selfservice/api/ccbConfiguration' doesn't match 'null /favicon.*'
2022-03-30 18:31:56.148 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [OrServerWebExchangeMatcher OrServerWebExchangeMatcher.java:60->lambda$matches$2] : No matches found
2022-03-30 18:31:56.149 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [NegatedServerWebExchangeMatcher NegatedServerWebExchangeMatcher.java:49->lambda$matches$0] : matches = true
2022-03-30 18:31:56.149 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [AndServerWebExchangeMatcher AndServerWebExchangeMatcher.java:61->lambda$matches$0] : Trying to match using MediaTypeRequestMatcher [matchingMediaTypes=[text/html], useEquals=false, ignoredMediaTypes=[*/*]]
2022-03-30 18:31:56.150 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [MediaTypeServerWebExchangeMatcher MediaTypeServerWebExchangeMatcher.java:84->matches] : httpRequestMediaTypes=[text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8]
2022-03-30 18:31:56.150 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [MediaTypeServerWebExchangeMatcher MediaTypeServerWebExchangeMatcher.java:86->matches] : Processing text/html
2022-03-30 18:31:56.151 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [MediaTypeServerWebExchangeMatcher MediaTypeServerWebExchangeMatcher.java:98->matches] : text/html .isCompatibleWith text/html = true
2022-03-30 18:31:56.152 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [AndServerWebExchangeMatcher AndServerWebExchangeMatcher.java:66->lambda$matches$4] : All requestMatchers returned true
2022-03-30 18:31:56.152 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [WebSessionServerRequestCache WebSessionServerRequestCache.java:76->lambda$saveRequest$1] : Request added to WebSession: '/wasd/selfservice/api/ccbConfiguration'
2022-03-30 18:31:56.163 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [DefaultServerRedirectStrategy DefaultServerRedirectStrategy.java:54->lambda$sendRedirect$0] : Redirecting to 'https://s0144821.company.com:9999/uaa/oauth/authorize?response_type=code&client_id=wasdssa&state=CPaWn5UE9oBzjPm-efV2mlONTXbNrLz5i_DZAy9KrS4%3D&redirect_uri=https://localhost:9093/wasdservice/login/oauth2/code/mdc'
2022-03-30 18:31:56.166 [lettuce-kqueueEventLoop-5-1] DEBUG 90357 --- [HttpWebHandlerAdapter LogFormatUtils.java:119->traceDebug] : [cc95c8f2-1] Completed 302 FOUND
Sample Application
I created a minimalistic project where you can see
all the details of the configuration and execute it so that you can verify for yourself that indeed this is happening.
(you are going to need a Redis instance and an Oauth server)
https://github.com/fiallega/spring-session-oauth-gateway
Configuration
The WebSecurity Configuration is as follows:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf().disable();
http.headers()
.referrerPolicy(ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
http.authorizeExchange()
.pathMatchers("/login**","/entries/**").permitAll()
.anyExchange()
.authenticated()
.and()
.oauth2Login();
return http.build();
}
and the gateway configuration is
cloud:
gateway:
routes:
- id: ssa-resources
uri: https://api.publicapis.org
predicates:
- Path=/entries
filters:
- TokenRelay=
What I see is that after reloading the application for some reason instead of making calls to the downstream servers, checks on mediatype happens and ends up with a 302 redirect when it should get a 200.
So why is it that a reload of an application that is using spring session is not able to "pickup" where it left, and this weird redirect happening after reloading the application if an active session exists in Redis?
Thanks
The solution was found adding the authorizedClientRepository bean to the WebSecurityConfig
#Bean
public ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
return new WebSessionServerOAuth2AuthorizedClientRepository();
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf().disable();
http.headers()
.referrerPolicy(ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
http.authorizeExchange()
.pathMatchers("/login**","/entries/**").permitAll()
.anyExchange()
.authenticated()
.and()
.oauth2Login(l -> l
.authorizedClientRepository(authorizedClientRepository()));
return http.build();
}
I want to give credit to the author of this article where I found the solution and the github repository he provided.
He provides the following comment to the solution:
/Most of the spring security classes that store data are already implemented using WebSession.
The only one that is not is {#link ServerOAuth2AuthorizedClientRepository} so we define that bean ourselves.
#return ServerOAuth2AuthorizedClientRepository/
I am making a rest call using restemplate and payload is XML. (we are dealing with legacy servers) But When I debug XML at server which is a spring application (legacy, but here irrelevant) , I see that the xml payload enclosed with "DeferredDocumentImpl"
Client code:
This is a new piece which is being added and is straightforward (so may be issue is here) Also I am testing by faking xml payload jsut to check parsing:
.....
String xmlString ="<?xml version=\"1.0\" ?>\n"
+ "<myroot>\n"
//node values
+ "</myroot>";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xml);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_XML);
HttpEntity<Document> request = new HttpEntity<>(document, headers);
final ResponseEntity<String> response = restTemplate.postForEntity(uri, request, String.class);
when I debugged outgoing call I see xml payload enclosed with "DeferredDocumentImpl"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "POST /myuri HTTP/1.1[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: text/plain, application/xml, text/xml, application/json, application/*+xml, application/*+json, */*[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "X-Forwarded-For: value[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Type: text/xml[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Length: 277[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: localdev.stanfordhealthcare.org:8443[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.11 (Java/12.0.2)[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "<DeferredDocumentImpl><?xml version="1.0" encoding="UTF-16"?><myroot>[\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> " <notification>[\n]"
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> " <type>3</type>[\n]"
/////
22:17:02.879 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "</myroot></DeferredDocumentImpl>"
22:17:08.324 [poolScheduler1] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Closing expired connections
Server code :
This is a working production legacy code and it does process xml payload properly .So I dont think there is issue here as it process same xml payload from other clients.
#SuppressWarnings("unchecked")
public static <T> T parseRequestObject(Class<T> pojoClass, HttpServletRequest request) {
T pojoObject = null;
String xml = IOUtils.toString(request.getReader());
...
}
So here When I debug xml I see the xml payload enclosed with "DeferredDocumentImpl"
"<DeferredDocumentImpl><?xml version="1.0" encoding="UTF-16"?><myroot> <..></DeferredDocumentImpl>"
Q:I am not sure from where this "DeferredDocumentImpl" is coming and why and how to fix it.
I solved it and the error is in a because of legacy handling of payload).
It is a REST call but content acceptable is plain text (as xml) not as XML document!
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-For", "value");
headers.setContentType(MediaType.TEXT_XML);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));//This is by default
HttpEntity<String> request = new HttpEntity<String>(xmlString, headers);
Config Server bootstrap.yml:
spring:
application:
name: configserver
profiles:
active: vault
cloud:
config:
server:
vault:
host: ${vault_server_host:localhost}
port: ${vault_server_port:8200}
scheme: ${vault_server_scheme:https}
backend: ${vault_backend:configserver}
Vault secrets:
$ vault kv get configserver/configclient
=== Data ===
Key Value
--- -----
foo VAUUULT
So, I'm able to get config values using curl:
$ curl -X GET http://localhost:8888/configclient/default -H "X-Config-Token: f7b238dd-425f-52f8-2104-1e37ecf65ede"
{
"name":"configclient",
"profiles":[
"default"
],
"label":null,
"version":null,
"state":null,
"propertySources":[
{
"name":"vault:configclient",
"source":{
"foo":"VAUUULT"
}
}
]
}
So, I've tried to get foo value from Config server from Config client. Config client bootstrap.yml:
spring:
application:
name: configclient
cloud:
config:
uri: http://localhost:8888
headers:
X-Config-Token: ${vault_token}
However, it seems that Config client is not able to locate Config server:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
2018-07-12 10:03:53.809 INFO 15448 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:8888
2018-07-12 10:03:54.239 WARN 15448 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Could not locate PropertySource: 400 null
2018-07-12 10:03:54.256 INFO 15448 --- [ main] c.t.i.t.s.t.TdevConfigclientApplication : No active profile set, falling back to default profiles: default
So then, it's getting me that:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'foo' in value "${foo}"
foo is configured as #Value("${foo}"):
#SpringBootApplication
#RestController
public class TdevConfigclientApplication {
#RequestMapping("/")
public String home() {
return "Hello World! " + this.foo;
}
#Value("${foo}")
private String foo;
public static void main(String[] args) {
SpringApplication.run(TdevConfigclientApplication.class, args);
}
}
Here you can see a more detailed config client trace snippet:
2018-07-12 10:29:05.249 INFO 17299 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:8888
2018-07-12 10:29:05.457 DEBUG 17299 --- [ main] o.s.web.client.RestTemplate : Created GET request for "http://localhost:8888/configclient/default"
2018-07-12 10:29:06.023 DEBUG 17299 --- [ main] o.s.web.client.RestTemplate : Setting request Accept header to [application/json, application/*+json]
2018-07-12 10:29:06.092 DEBUG 17299 --- [ main] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#3bb6b7e25 pairs: {GET /configclient/default HTTP/1.1: null}{Accept: application/json, application/*+json}{User-Agent: Java/10.0.1}{Host: localhost:8888}{Connection: keep-alive}
2018-07-12 10:29:06.121 DEBUG 17299 --- [ main] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#3b1892d05 pairs: {null: HTTP/1.1 400}{Content-Type: application/json;charset=UTF-8}{Transfer-Encoding: chunked}{Date: Thu, 12 Jul 2018 08:29:06 GMT}{Connection: close}
2018-07-12 10:29:06.145 DEBUG 17299 --- [ main] o.s.web.client.RestTemplate : GET request for "http://localhost:8888/configclient/default" resulted in 400 (null); invoking error handler
2018-07-12 10:29:06.162 WARN 17299 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Could not locate PropertySource: 400 null
Any ideas?
In your bootstrap.yml you need to replace spring.cloud.config.headers by this:
spring:
application:
name: configclient
cloud:
config:
uri: http://localhost:8888
token : ${vault_token}
You can see doc http://cloud.spring.io/spring-cloud-config/1.4.x/single/spring-cloud-config.html
I am able to get a test running for a Spring Boot Project but I'm always getting a 404 on the #State test.
#TargetRequestFilter
public void exampleRequestFilter(HttpRequest request) {
System.out.println(request.toString());
request.addHeader("Authorization", JIMMY_CARTER_TOKEN);
}
#BeforeClass
public static void setupApplication() {
SpringApplication application = new SpringApplication(App.class);
application.setAdditionalProfiles("integration");
application.run("--server.port=9000");
}
#TestTarget
public final HttpTarget target = new HttpTarget("http", "127.0.0.1", 9000);
#State("user id") // Method will be run before testing interactions that require "default" or "no-data" state
public void toUserId() {
System.out.println("Test User Id");
}
What's strange is I can tell it's hitting the right endpoint by printing out the request information and the Authorization header. I put a debug statement in and verified that I can call with the same credentials and endpoint as the test. However the test is always failing with a 404. Is there something I'm missing in my setup?
"request": {
"method": "GET",
"path": "/api/user/XXXXXX"
},
"response": {
"status": 200,
"headers": {
"content-type": "application/vnd.api+json;charset=UTF-8"
},
"body": ...
},
"providerStates": [
{
"name": "user id"
}
]
}
You can see what requests are being made by enabling debug logging with the Apache HTTP Client and the pact-jvm libraries. For Apache HTTP Client, please refer to https://hc.apache.org/httpcomponents-client-ga/logging.html.
For an example of what the debug logs you are looking for, this is from the example ContractTest from pact-jvm (https://github.com/DiUS/pact-jvm/blob/master/pact-jvm-provider-junit/src/test/java/au/com/dius/pact/provider/junit/ContractTest.java):
13:09:20.012 [Test worker] DEBUG au.com.dius.pact.provider.ProviderClient - Making request for provider au.com.dius.pact.provider.ProviderInfo(http, localhost, 8332, /, myAwesomeService, null, null, au.com.dius.pact.provider.junit.target.HttpTarget$$Lambda$14/771479970#1dec1536, null, null, false, null, changeit, null, true, false, true, null, [], []):
13:09:20.018 [Test worker] DEBUG au.com.dius.pact.provider.ProviderClient - method: GET
path: /data
query: [:]
headers: [:]
matchers: MatchingRules(rules=[:])
generators: Generators(categories={})
body: OptionalBody(state=MISSING, value=null)
13:09:20.475 [Test worker] INFO au.com.dius.pact.provider.junit.ContractTest - exampleRequestFilter called: GET http://localhost:8332/data HTTP/1.1
13:09:20.537 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /data HTTP/1.1
13:09:20.538 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:8332
13:09:20.538 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
13:09:20.551 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_131)
13:09:20.553 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
13:09:20.553 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /data HTTP/1.1[\r][\n]"
13:09:20.554 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: localhost:8332[\r][\n]"
13:09:20.555 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
13:09:20.558 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_131)[\r][\n]"
13:09:20.559 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
13:09:20.560 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
13:09:20.774 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 204 No Content[\r][\n]"
13:09:20.775 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Sat, 23 Sep 2017 03:09:20 GMT[\r][\n]"
13:09:20.775 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 << "Server: rest-client-driver(1.1.45)[\r][\n]"
13:09:20.779 [Test worker] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
13:09:20.784 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 204 No Content
13:09:20.785 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sat, 23 Sep 2017 03:09:20 GMT
13:09:20.785 [Test worker] DEBUG org.apache.http.headers - http-outgoing-0 << Server: rest-client-driver(1.1.45)
13:09:20.842 [Test worker] DEBUG au.com.dius.pact.provider.ProviderClient - Received response: HTTP/1.1 204 No Content
13:09:20.867 [Test worker] DEBUG au.com.dius.pact.provider.ProviderClient - Response: [statusCode:204, headers:[Date:Sat, 23 Sep 2017 03:09:20 GMT, Server:rest-client-driver(1.1.45)]]
13:09:21.724 [Test worker] DEBUG au.com.dius.pact.model.Matching$ - Found a matcher for text/plain -> Some((text/plain,au.com.dius.pact.matchers.PlainTextBodyMatcher#29c3e77b))
returns a response which
has status code 204 (OK)
has a matching body (OK)
I'm playing around with Spring OAuth, implemented an authorization server and a resource server. The resource server uses user-info-uri to decode a token.
Methods (some) in the resource server's controllers are protected by #RolesAllowed (also tried #PreAuthorize, same effect).
#RolesAllowed("ROLE_USER")
//#PreAuthorize("hasRole('ROLE_USER')")
#RequestMapping(value = "/test-user", method = RequestMethod.GET)
public String testUser() {
return "You are User!";
}
There are three users, managed on the authorization server side: user1 with ROLE_ADMIN, user2 and user3 with ROLE_USER.
The resource service accepts the token, generated by the authorization server (password grant flow) and asks the user-info-uri about the principal details. So far works as designed.
But what then happens, is what I do not understand. The principal structure (say, for user2, having ROLE_USER), contains a correct authority (for the example purpose I made a manual call to the user-info-uri):
"principal": {
"password": null,
"username": "user2",
"authorities": [
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
And it seems to be correctly deserialized at the resource server side:
2016-08-31 12:30:37.530 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.a.i.a.MethodSecurityInterceptor : Secure object: ReflectiveMethodInvocation: public java.lang.String org.cftap.OAuthResourceController.testUser(); target is of class [org.cftap.OAuthResourceController]; Attributes: [ROLE_USER, ROLE_USER]
2016-08-31 12:30:37.530 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.a.i.a.MethodSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.provider.OAuth2Authentication#ed03ae2: Principal: user2; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: {authority=ROLE_USER}
2016-08-31 12:30:37.530 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#4cf62e16, returned: 0
2016-08-31 12:30:37.530 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.annotation.Jsr250Voter#11e4338f, returned: -1
2016-08-31 12:30:37.530 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter#3d5cb07f, returned: -1
2016-08-31 12:30:37.531 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter#2724a21f, returned: 0
2016-08-31 12:30:37.536 DEBUG 32992 --- [nio-9998-exec-1] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Wed Aug 31 12:30:37 CEST 2016, principal=user2, type=AUTHORIZATION_FAILURE, data={type=org.springframework.security.access.AccessDeniedException, message=Access is denied}]
2016-08-31 12:30:37.546 DEBUG 32992 --- [nio-9998-exec-1] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is not anonymous); delegating to AccessDeniedHandler
But, as you see in the debug log, the RoleVoter (and JSR250 one) votes against it (although the allowed role and the authority of the principal fit together), hence sending 403 back.
Did I miss something important?
Thanks in advance.
Try with
#RolesAllowed("USER") instead of #RolesAllowed("ROLE_USER").
Eventually you could use hasAuthority("ROLE_USER") or hasRole("USER") instead of hasRole("ROLE_USER") .
These are changes from Spring 4, you are probably using some old Spring 3 documentation / articles.