Accessing OAuth2 protected Resource with Spring WebClient where the token provider is also ssl protected - spring

trying to use webclient to access an OAuth2 protected token and resource server. The requirement is to add certificates to both requests. Here is more or less how I try to add certs to my request:
HttpClient resourceServerHttpClient = HttpClient.create()
.tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000))
.secure(sslContextSpec -> {
sslContextSpec.sslContext(oAuth2ClientSSLPropertiesConfigurer.getConstructedSslContexts().get("cpd"));
});
ClientHttpConnector customHttpConnector = new ReactorClientHttpConnector(resourceServerHttpClient);
return WebClient.builder()
.baseUrl(lisClientBaseUrl)
.exchangeStrategies(exchangeStrategies)
.clientConnector(customHttpConnector)
.filter(oauth)
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
})
.build();
Question 1 : The snippet above passes certificates to the resource server request only, as far as I can understand. Found https://neuw.medium.com/spring-boot-oauth2-mutual-tls-client-client-credentials-grant-3cdb7a2a44ea where my customization fits Scenario-3. But then the only way is to switch fully reactive context (not servlet) in my api, to access DefaultReactiveOAuth2AuthorizedClientManager, authorizedClientProvider etc. Is there a simpler way to achieve this?
Question 2 : Is there a way to see more details regarding the request to the token provider - to make sure that certificates are sent.

Related

How to block access to my API from Postman/Other API's/etc.. (Spring Boot)

I'm developping a Rest Api with Spring Boot and Spring Security.
I have both public and private areas and i used Spring Security for authentication (for the private area).
The problem is that i configured CORS and it blocks requests if i call public endpoints from unauthorized url's but and i was surprised that if i call it from Postman or another Spring Boot App using RestTemplate, the CORS don't block the request and return the result.
I read on Internet that the CORS is only blocking calls from browsers.. So how can i protect the public part of my API from calling it from Postman or other API's ?
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200","http://localhost:4201"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
I am afraid there is no solution for that. In Postman, you can add any headers you want. So it is possible to mimic to any client if you have all the necessary tokens.
Also, CORS is slightly for different purpose:
The use-case for CORS is simple. Imagine the site alice.com has some data that the site bob.com wants to access. This type of request traditionally wouldn’t be allowed under the browser’s same origin policy. However, by supporting CORS requests, alice.com can add a few special response headers that allows bob.com to access the data.
You can find additional info here: https://medium.com/#baphemot/understanding-cors-18ad6b478e2b
private Map<String, String> getRequestHeadersInMap(HttpServletRequest request) {
Map<String, String> result = new HashMap<>();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
result.put(key, value);
}
return result;
}

Invalid JWToken: kid is a required JOSE Header

I am trying to implement an Oauth2 Authorization Server with SpringBoot using this guide as a reference.
My keystore has a single key. I have successfully managed to create a JWToken (I can check it at jwt.io).
I have also a test Resource Server. When I try to access any endpoint I receive the following message:
{
"error": "invalid_token",
"error_description": "Invalid JWT/JWS: kid is a required JOSE Header"
}
The token really does not have a kid header but I can not figure out how to add it. I can only add data to its payload, using a TokenEnchancer. It also seems that I am not the first one with this issue.
Is there any way to add this header or, at least, ignore it at the resource server?
I've been working on an article that might help you out here:
https://www.baeldung.com/spring-security-oauth2-jws-jwk
So, to configure a Spring Security OAuth Authorization Server to add a JWT kid header, you can follow the steps of section 4.9:
create a new class extending the JwtAccessTokenConverter
In the constructor:
configure the parent class using the same approach you've been using
obtain a Signer object using the signing key you're using
override the encode method. The implementation will be the same as the parent one, with the only difference that you’ll also pass the custom headers when creating the String token
public class JwtCustomHeadersAccessTokenConverter extends JwtAccessTokenConverter {
private JsonParser objectMapper = JsonParserFactory.create();
final RsaSigner signer;
public JwtCustomHeadersAccessTokenConverter(KeyPair keyPair) {
super();
super.setKeyPair(keyPair);
this.signer = new RsaSigner((RSAPrivateKey) keyPair.getPrivate());
}
#Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication));
} catch (Exception ex) {
throw new IllegalStateException("Cannot convert access token to JSON", ex);
}
Map<String, String> customHeaders = Collections.singletonMap("kid", "my_kid");
String token = JwtHelper.encode(content, this.signer, this.customHeaders)
.getEncoded();
return token;
}
}
Then, of course, create a bean using this converter:
#Bean
public JwtAccessTokenConverter accessTokenConverter(KeyPair keyPair) {
return new JwtCustomHeadersAccessTokenConverter(keyPair);
}
Here I used a KeyPair instance to obtain the signing key and configure the converter (based on the example of the article), but you might adapt that to your configuration.
In the article I also explain the relevant endpoints provided by the Spring Security OAuth Authentication Server.
Also, regarding #Ortomala Lokni's comment, I wouldn't expect Spring Security OAuth to add any new features at this point. As an alternative, you probably can wait to have a look at Spring Security's Authorization Server features, planned to be released in 5.3.0
I managed to solve it by changing the parameter used to identify the URL where the clients will retrieve the pubkey.
On application.properties, instead of:
security.oauth2.resource.jwk.key-set-uri=http://{auth_server}/.well-known/jwks.json
I used:
security.oauth2.resource.jwt.key-uri=http://{auth_server}/oauth/token_key
If I understood correctly, the key-set-uri config points to an endpoint that presents a set of keys and there is the need for a kid. On the other side key-uri config points to an endpoint with a single key.

How to apply Spring Cloud Gateway GlobalFilter on WebClient in a RouterFuncation bean?

Currently I am trying to use Spring Cloud Gateway(Spring Cloud version: Finchley.M5) in a edge-service, my sample has a Spring Session header(X-AUTH-TOKEN) token based authentication.
In Gateway specific RouteLocator, the authentication works well, because the built-in GlobalFilters are applied on the RouteLocator when it passes request to downstream microservice.
But I want to create a generic RouterFunction to consume some resources of downstream services and aggregate them together in a new edgeservice, the GlobalFileters will not apply on my webclient bean.
#Bean
RouterFunction<ServerResponse> routes(WebClient webClient) {
log.debug("authServiceUrl:{}", this.authServiceUrl);
log.debug("postServiceUrl:{}", this.postServiceUrl);
log.debug("favoriteServiceUrl:{}", this.favoriteServiceUrl);
return route(
GET("/posts/{slug}/favorited"),
(req) -> {
Flux<Map> favorites = webClient
.get()
.uri(favoriteServiceUrl + "/posts/{slug}/favorited", req.pathVariable("slug"))
.retrieve()
.bodyToFlux(Map.class);
Publisher<Map> cb = from(favorites)
.commandName("posts-favorites")
.fallback(Flux.just(Collections.singletonMap("favorited", false)))
.eager()
.build();
return ok().body(favorites, Map.class);
}
)
...
Is there a simple solution to apply Gateway Filter also work on the RouterFunction, thus my header token based authentication can work automatically?
Or there is a simple way to pass current X-AUTH-TOKEN header into the downstream webclient request headers?
In traditional MVC, there is a RequestContext used to get all headers from current request context and pass them to the downstream request in a global filter. Is there an alternative of RequestContext in webflux to read all headers from current request context?
The complete codes is here.

Setting OAuth2 token for RestTemplate in an app that uses both #ResourceServer and #EnableOauth2Sso

On my current project I have an app that has a small graphical piece that users authenticate using SSO, and a portion that is purely API where users authenticate using an Authorization header.
For example:
/ping-other-service is accessed using SSO.
/api/ping-other-service is accessed using a bearer token
Being all cloud native our app communicates with other services that uses the same SSO provider using JWT tokens (UAA), so I figured we'd use OAuth2RestTemplate since according to the documentation it can magically insert the authentication credentials. It does do that for all endpoints that are authenticated using SSO. But when we use an endpoint that is authed through bearer token it doesn't populate the rest template.
My understanding from the documentation is that #EnableOAuth2Client will only extract the token from a SSO login, not auth header?
What I'm seeing
Failed request and what it does:
curl -H "Authorization: Bearer <token>" http://localhost/api/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 401
Successful request and what it does:
Chrome http://localhost/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 200
How we worked around it
To work around this I ended up creating the following monstrosity which will extract the token from the OAuth2ClientContext if it isn't available from an authorization header.
#PostMapping(path = "/ping-other-service")
public ResponseEntity ping(#PathVariable String caseId, HttpServletRequest request, RestTemplate restTemplate) {
try {
restTemplate.postForEntity(adapterUrl + "/webhook/ping", getRequest(request), Map.class);
} catch (HttpClientErrorException e) {
e.printStackTrace();
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
}
return new ResponseEntity(HttpStatus.OK);
}
private HttpEntity<?> getRequest(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + getRequestToken(request));
return new HttpEntity<>(null, headers);
}
private String getRequestToken(HttpServletRequest request) {
Authentication token = new BearerTokenExtractor().extract(request);
if (token != null) {
return (String) token.getPrincipal();
} else {
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken != null) {
return accessToken.getValue();
}
}
throw new ResourceNotFound("No valid access token found");
}
In the /api/** resources there is an incoming token, but because you are using JWT the resource server can authenticate without calling out to the auth server, so there is no OAuth2RestTemplate just sitting around waiting for you to re-use the context in the token relay (if you were using UserInfoTokenServices there would be one). You can create one though quite easily, and pull the incoming token out of the SecurityContext. Example:
#Autowired
private OAuth2ProtectedResourceDetails resource;
private OAuth2RestTemplate tokenRelayTemplate(Principal principal) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
details.getTokenValue();
OAuth2ClientContext context = new DefaultOAuth2ClientContext(new DefaultOAuth2AccessToken(details.getTokenValue()));
return new OAuth2RestTemplate(resource, context);
}
You could probably turn that method into #Bean (in #Scope("request")) and inject the template with a #Qualifier if you wanted.
There's some autoconfiguration and a utility class to help with this pattern in Spring Cloud Security, e.g: https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/client/AccessTokenContextRelay.java
I came across this problem when developing a Spring resource server, and I needed to pass the OAuth2 token from a request to the restTemplate for a call to a downstream resource server. Both resource servers use the same auth server, and I found Dave's link helpful but I had to dig a bit to find out how to implement this. I ended up finding the documentation here, and it turn's out the implemetation was very simple. I was using #EnableOAuth2Client, so I had to create the restTemplate bean with the injected OAuth2ClientContext and create the appropriate resource details. In my case it was ClientCredentialsResourceDetails. Thanks for all great work Dave!
#Bean
public OAuth2RestOperations restTemplate (OAuth2ClientContext context) {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
// Configure the details here
return new OAuth2RestTemplate(details, context)
}
#Dave Syer
My UAA service is also an oauth2 client, which needs to relay JWT tokens coming in from Zuul. When configuring the oauth2 client the following way
#Configuration
#EnableOAuth2Client
#RibbonClient(name = "downstream")
public class OAuthClientConfiguration {
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
}
I do get a 401 response from the downstream service as my access token has a very short validity and the AccessTokenContextRelay does not update an incoming access token (Zuul does renew expired access tokens by the refresh token).
The OAuth2RestTemplate#getAccessToken will never acquire a new access token as the isExpired on the access token stored by the AccessTokenContextRelay drops the validity and refresh token information.
How can this by solved?

Can't figure out if OWIN is intercepting requests to api from client

I have an Asp.net webapi with JWT authentication using OWIN middle ware.
My resource server and the authorization server are same. I am able to get the token from the token endpoint. ValidateClientAuthentication and GrantResourceOwnerCredentials methods are hit successfully.
However when I try to access a protected(with [Authorize]) api (with authorization header set to bearer token) I only get "Authorization has been denied for this request".
I have overridden ValidateAuthorizeRequest method just to see if it gets hit when the api call is made via Postman. However it is never hit.
I am trying to figure out a way to see if at all OWIN is intercepting calls to the api other than the calls to the token endpoint.
Is there any way or methods to override so that I can debug and see where in the pipeline the request is being rejected and why.
As of now I make the call via Postman and get an unauthorized response.
Any help would be greatly appreciated.
this is difficult to answer without seeing what you've done.
I am wondering if you have wired things up correctly. Startup class is where you define your Provider and Token format and then you set your application to use those settings. Here is an example:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
int accessTokenExpiresInSeconds = ConfigurationHelper.GetAppSetting("AccessTokenExpirationInSeconds").ToInt();
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString(ConfigurationHelper.GetAppSetting("TokenEndPoint")),
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(accessTokenExpiresInSeconds),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationHelper.GetAppSetting("TokenIssuer"))
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
}
}
If that's not the issue then you can use my own article on OAuth2 and JWT, I've got a full example on how to set everything up and the code is on GitHub. Hopefully it will guide you in the right direction:
https://eidand.com/2015/03/28/authorization-system-with-owin-web-api-json-web-tokens/

Resources