We have problem with propagation of traceId in requests which are called by spring oauth2 module.
For instance consider authorization and resource server. In resource server we have spring security configuration to ensure get rsa public key from authorization server with following property:
security:
oauth2:
resource:
jwk:
key-set-uri: http://authorization-server:8080/key-set
When I call controller of resource server with jwt token, I can see in zipkin traces from resource server and authorization server as well, but there is no traceId propagation from resource server to authorization server.
First record is calling rest api to get resources, and second record is produced call to authorization server to find out public RSA key.
Related
I have simple resource server application with spring boot, this is yaml file:
server:
port: 8081
servlet:
context-path: /resource-server-jwt
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8083/auth/realms/rasool
Now, i want to make change in configuration or code to force spring security to validate JWT token with calling introspection endpoint of authorization server instead of local validation with keys, but i didn't find any way as spring security docs says.
Spring-boot spring.security.oauth2.resourceserver.jwt.* configuration properties are for JWT decoder.
For token introspection, use spring.security.oauth2.resourceserver.opaque-token.* properties instead (token being in whatever format, including JWT). "opaque" means that tokens are considered a "black-box" by resource-server which delegates validataion and attributes retrieval to authorization-server on introspection endpoint:
server:
port: 8081
servlet:
context-path: /resource-server-jwt
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: http://localhost:8083/auth/realms/rasool/protocol/openid-connect/token/introspect
client-id: change-me
client-secret: change-me
Introspection uri from .well-known/openid-configuration
If you are using Java configurationn the switch is about the same: replace http.oauth2ResourceServer().jwt()... with http.oauth2ResourceServer().opaqueToken()...
A few notes about declared clients on authorisation-server
Resource-servers introspect token on authorisation-server introspection endpoint using client-credentials flow: for each and every request it process, resource-servers will send a request to authorization-server to get token details. This can have serious performance impact. Are you sure you want to switch to token introspection?
As a consequence, in the properties above, you must configure a client with:
"Access Type" set to confidential
"Service Accounts Enabled" activated
Create one if you don't have yet. You'll get client-secret from "credentials tab" once configuration saved.
Note you should have other (public) clients to identify users (from web / mobile apps or REST client) and query your resource-server on behalf of those users.
From the authorization-server point of view, this means that access-tokens will be issued to a (public) client and introspected by antoher (confidential) client.
Complete working sample here
It does a few things useful for resource-servers:
authorities mapping (choose attributes to parse user authorities from, prefix & case processing)
CORS configuration
stateless-session management
CSRF with Cookie repo
anonymous enabled for a list of configured public routes
401 (unauthorized) instead of 302 (redirect to login) when trying to access protected resources with missing or invalid Authorization
I have a spring application that verifies a JWT token on the rest endpoint.
Using SecurityChain
.oAuth2ResourceServer()
.jwt()
This seems to create a JwtAuthenticationToken in the ReactiveSecurityContextHolder.
I then want to flow the input from this endpoint where the client is authenticated by checking the bearer token. And then call another rest service using a webClient. This web client needs to authenticate with grant type password with the external service using a different OAuth server and get is own bearer token.
The problem is that the web client uses the ReactiveSecurityContextHolder that contains the authenticated JWT. And tries to use this information rather than connect and authenticate my app to the rest endpoint.
I have set up the Yaml to register my client
spring:
security:
oauth2:
client:
registration:
Myapp:
client-id:
client-secret:
token-uri:
authorization-grant-type:
Then adding a filter function of
ServerOAuth2AuthorizedClientExchangeFilterFunction
But I get principalName cannot be empty as it seems to reuse the security context from verifying the caller on the rest endpoint in my application.
How should it be designed or samples to show how you can use different security contexts or get tokens differently between service to service calls?
You are correct that the design of ServerOAuth2AuthorizedClientExchangeFilterFunction is designed to be based on the currently-authorized client, which you've explained that you don't want to use in this case.
You've indicated that you want to use the client's credentials as the username and password for the Resource Owner Password Grant. However, there's nothing in Spring Security that is going to do that.
However, you can use WebClientReactivePasswordTokenResponseClient directly in order to formulate the custom request yourself.
Briefly, this would be a custom ExchangeFilterFunction that would look something like:
ClientRegistrationRespository clientRegistrations;
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest>
accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return this.clientRegistrations.findByRegistrationId("registration-id")
.map(clientRegistration -> new OAuth2PasswordGrantRequest(
clientRegistration,
clientRegistration.getClientId(),
clientRegistration.getClientSecret())
.map(this.accessTokenResponseClient::getTokenResponse)
.map(tokenResponse -> ClientRequest.from(request)
.headers(h -> h.setBearerAuth(tokenResponse.getAccessToken().getTokenValue())
.build())
.flatMap(next::exchange);
}
(For brevity, I've removed any error handling.)
The above code takes the following steps:
Look up the appropriate client registration -- this contains the provider's endpoint as well as the client id and secret
Construct an OAuth2PasswordGrantRequest, using the client's id and secret as the resource owner's username and password
Perform the request using the WebClientReactivePasswordTokenResponseClient
Set the access token as a bearer token for the request
Continue to the next function in the chain
Note that to use Spring Security's OAuth 2.0 Client features, you will need to configure your app also as a client. That means at least changing your DSL to include .oauth2Client() in addition to .oauth2ResourceServer(). It will also mean configuring a ClientRegistrationRepository. To keep my comment focused on filter functions, I've left that detail out, but I'd be happy to help there, too, if necessary.
I'm having a problem with SESSION cookie being reset by Spring Cloud Gateway after a call to a resource server.
I have an Angular application, a Spring Cloud Gateway application, an external Authorization Server and a Resource Server of my own.
The Angular application first authorizes via Spring Cloud Gateway app (who delegates this work to external Authorization Server using OAuth2) and receives a SESSION cookie. At this point the user is authenticated and Authentication object is available in Spring Cloud Gateway app.
Next, the Angular app calls an endpoint of Spring Cloud Gateway app, which actually forwards the call to the Resource Server (and includes the Bearer token in the call, so the call works fine), the Resource server returns some result, which is successfully send back through the Spring Cloud Gateway app to the Angular app. BUT alongside successful response the Spring Cloud Gateway app sends this header:
set-cookie: SESSION=; Max-Age=0; Expires=Sat, 17 Aug 2019 20:39:44 GMT; Path=/; HTTPOnly
which kills the cookie on the client side and makes subsequent calls impossible, even though the Authentication object is still alive and the session looks to fine as well.
Does anyone know what can be the reason of such behavior?
We had the exact issue in our WebFlux resource servers -- the API gateway would proxy a request to a resource server, so the first request worked, but subsequent requests would try to authenticate again because the SESSION cookie was cleared out, resulting in some X-XSRF-TOKEN errors.
We solved this by adding .requestCache().requestCache(NoOpServerRequestCache.getInstance()) to our securityWebFilterChain bean definition in our WebFlux resource servers.
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
class ResourceServerConfiguration {
#Value('${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}')
String jwkSetUri
#Bean
SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.requestCache().requestCache(NoOpServerRequestCache.getInstance()).and()
.httpBasic().disable()
.formLogin().disable()
.oauth2ResourceServer().jwt().jwkSetUri(jwkSetUri)
.and().and()
.authorizeExchange()
.pathMatchers("/v1/**").authenticated()
}
}
In the "classic" MVC world, you would configure your ResourceServerConfigurer classes like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
Update 11/22/2022
Our microservice architecture has begun to expand, and we started seeing this issue again on services not owned by our team. Turns out they had stateful web services, meaning, sessions were created when calling them which caused the SESSION cookie to get overridden on the Spring Cloud Gateway client application. We applied this to our configuration to permanently resolve this issue:
server:
reactive:
session:
cookie:
name: SESSION_${spring.application.name}
👆 This eliminates an issue where SESSION cookies from other web service calls collide with the Gateway client's SESSION cookie.
I had encountered the same case.
Did the external Authorization Server produce a cookie which is base64 encode?
Such as Set-Cookie: SESSION=YWZjOTc4YmUtOTNmNy00N2UxLTg0NjgtYWJlNWMwZmNiOWUx
If so, it will cause the problem.
The CookieWebSessionIdResolver defined in Spring Web used by Spring Cloud Gateway does not deal with the base64 encoded cookie values. Instead, it directly uses the raw cookie value to find the corresponding session in the storage. Obviously, no Authentication object will be found. So that Spring Cloud Gateway choose to kill the "invalid" cookie given by the Angular app.
There are two solutions given below.
Disable base64 encoding of cookie values in the external Authorization Server if it is also managed by you.
Override WebSessionIdResolver to change the default behaviour so as to decode cookie values when reading by session manager. And don't forget to register it as a Spring Bean in your Spring Cloud Gateway implementation.
In my case, Solution 1 was choosen. My Authorization Server uses Spring Security + Spring Session. I changed the default settings of HttpSessionIdResolver like this.
CookieHttpSessionIdResolver cookieHttpSessionIdResolver = new CookieHttpSessionIdResolver();
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
defaultCookieSerializer.setUseBase64Encoding(false);
defaultCookieSerializer.setCookieName("SESSION");
cookieHttpSessionIdResolver.setCookieSerializer(defaultCookieSerializer);
How can I get details from the OAuth2 SSO Principal into my JWT? (instance of OAuth2Authentication getDetails as OAuth2AuthenticationDetails getDecodedDetails returns null)
I have...
Angular 6 client w/ implicit login as acme client (using angular-oauth2-oidc)
Spring Boot OAuth2 Authorization Server with JWT TokenService configuration w/ 3rd party SSO to GitHub
Auth server is configured with acme as implicit and GitHub client for SSO
Auth server exposes a /login/github
Auth server exposes a /me (protected by ResourceServer config)
When I login...
Angular app redirects to Auth service login
Auth service redirects to GitHub
[User Authenticates]
GitHub redirects to Auth Service
Auth Service initiates a session and issues a token
Auth Service redirects to Angular
The browser token is a proper JWT
Now, when I communicate with Auth Service /me:
Directly, I get a Principal that contains ALL of the details from GitHub (yay)
Indirectly from the Angular application passing the token via Authorization: Bearer ... header, I get a Principal that contains bare minimum OAuth client info for acme client (ugh)
I've tried a custom TokenEnhancer, but the OAuth2Authentication instance is already the bare minimum with no details. And, when the call is initiated from Angular, it doesn't have the same session cookie as when I call it directly (I don't want to share session - I want to put the details in the JWT).
[Update #1]
I tried a custom JwtAccessTokenConverter and used it in both of the #EnableAuthorizationServer and #EnableResourceServer (secures the /me endpoint) configuration classes. However it didn't work. I still get null details from OAuth2Authentication.
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(new CustomTokenConverter());
The way Spring Lemon does this is replacing the OAuth2 and OpenID connect user services (see spring security docs). See LemonOAuth2UserService and LemonOidcUserService for details. For statelessness, it passes the client a shortlived JWT token as a param to targetUrl, as you can see in its OAuth2AuthenticationSuccessHandler class. It uses some cookies mechanism for doing all this statelessly, which can be further understood by looking at its HttpCookieOAuth2AuthorizationRequestRepository and how it's configured.
Here is an article explaining this in more details: https://www.naturalprogrammer.com/blog/1681261/spring-security-5-oauth2-login-signup-stateless-restful-web-services .
I try to implement a simple OAuth2 "Client Authentication with Signed JWT" Demo App using Spring Boot and Keycloak as AuthService.
The idea is:
one secured REST service "The Producer"
offering an endpoint GET /person for all users/principals with the role "read_person"
offering an endpoint POST /person for all users/principals with the role "write_person"
another (unsecured) REST service "The Consumer"
offering an enpoint /api open for everybody
calling internal the "producer" viaFeignclient using an RequestInterceptor to pass the AccessToken (signed JWT / JWS)
I read about the docs:
http://www.keycloak.org/docs/latest/securing_apps/topics/oidc/java/client-authentication.html
saying:
Once the client application is started, it allows to download its public >key in JWKS format using a URL such as http://myhost.com/myapp/k_jwks, >assuming that http://myhost.com/myapp is the base URL of your client >application. This URL can be used by Keycloak (see below).
During authentication, the client generates a JWT token and signs it with >its private key and sends it to Keycloak in the particular backchannel >request (for example, code-to-token request) in the client_assertion >parameter.
I googled a lot to find tutorials/demos or docs about this topic but failed so far.
So here my questions:
How do I implement this "k_jwk" endpoint? Do I simple build a #RestController by myself in "the Producer"? How do I configure Keycloak to get aware of this URL?
How do I implement my "Consumer" to get fresh signed JWT from Keycloak?
Update
Removed irritating PS statement.
You don't need to implement the k_jwk endpoint, this is handled by the adapter. Keycloak will by default look at http:///your.app.com/k_jwk(but if needed you can override that in the console).
Then you need to configure your Spring Boot client, just use the same properties as the keycloak.json but in the application.properties format:
...
keycloak.credentials.jwt.client-keystore-file=classpath:keystore-client.jks
keycloak.credentials.jwt.client-keystore-type=JKS
etc ...
You need a token to call the producerbut as you said the entry point will be an insecured endpoint so you might want to use a Service Account for this.
I hope this will help.
Update
I couldnt solve this issue but learned some things about singned JWT in the mean time:
create a so called "Bearer Token" by creating a Json Structure with all necessary claims (sub, nbf, exp ...) by yourself and sign/certificate it with your JKS/Private Key from Keycloak. There are some nice third party libs beside Keycloak to do this.
To get a real AccessToken (JWE/JWS) from Keycloak: send this static final Bearer Token to Keycloak at /auth/realms/$realm/protocol/openid-connect/token/introspect
with QueryParams:
grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$BEARER_TOKEN
Use the received real AccessToken to access the ResourceServer...