How to set HTTP Header for OAuth2RestTemplate - java

Am trying to use Spring Secruity's OAuth API to obtain an access token from an externally published API within a Spring MVC 4 based Web Services (not Spring Boot).
This curl command works (and its contents are all that I need to obtain an access token):
curl -X POST \
https://api.app.com/v1/oauth/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d'grant_type=client_credentials&client_id=bcfrtew123&client_secret=Y67493012'
Spring Security OAuth API:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
My code to obtain access token:
#RequestMapping(value = "/getAccessToken", method = RequestMethod.POST, consumes="application/x-www-form-urlencoded")
public OAuth2AccessToken getAccessToken(#RequestParam(value="client_id", required=true) String clientId, #RequestParam(value="client_secret", required=true) String clientSecret) throws Exception {
String tokenUri = "https://api.app.com/v1/oauth/token";
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(tokenUri);
resourceDetails.setClientId(clientId);
resourceDetails.setClientSecret(clientSecret);
resourceDetails.setGrantType("client_credentials");
resourceDetails.setScope(Arrays.asList("read", "write"));
DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
oauth2RestTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
OAuth2AccessToken token = oauth2RestTemplate.getAccessToken();
return token;
}
When I invoke the getAccessToken call from my local tomcat instance:
access_denied
error_description=Unable to obtain a new access token for resource 'null'.
The provider manager is not configured to support it.
Am suspecting the reason is that my Http Header's Content-Type is not set for
application/x-www-form-urlencoded
How do I do set that for:
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
If you notice, I am trying to set in inside the #RequestMapping and don't think that its working:
#RequestMapping(consumes="application/x-www-form-urlencoded")

The http headers for accessing the token in Oauth2Restemplate in case of Client credentials are set in below method of ClientCredentialsAccessTokenProvider (since grant type is client credentials)
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException,
OAuth2AccessDeniedException {
ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
return retrieveToken(request, resource, getParametersForTokenRequest(resource), new HttpHeaders());
}
We can set the http headers by having new custom Access token provider for client credentials and modifying the method as follows:
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
HttpHeaders headers1 = new HttpHeaders();
headers1.add("Content-Type", "application/x-www-form-urlencoded");
return retrieveToken(request, resource, getParametersForTokenRequest(resource), headers1);
}
You can keep the class same as ClientCredentialsAccessTokenProvider and add just the header lines.
Last step will be to set this new class as access token in configuration of Oauth2RestTemplate.
oauth2RestTemplate.setAccessTokenProvider(new ClientCredentialsCustomAccessTokenProvider());
This worked for me!

Here's another variation on the answer just to override the default Accept Header interceptor using a Lambda expression:
#Bean
protected RestTemplate restTemplate() {
return new RestTemplate() {
#Override
public <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) {
return request -> {
request.getHeaders().setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
};
}
};
}

If you are using Spring boot mention the authentication scheme as form, it will solve the issue.
security:
oauth2:
client:
clientAuthenticationScheme: form

Related

Spring Cloud Gateway and CORS header

I am trying to proxy-forward a request using org.springframework.cloud.gateway.mvc.ProxyExchange.
The call is working fine.
In my scenario I like to integrate it with the browser calls, but
get a CORS-header issue there.
(1) Source in the controller:
#RequestMapping(value = "/public**", method = RequestMethod.GET)
public ResponseEntity<byte[]> forwardHttpRequestForGet(
HttpServletRequest httpServletRequest,
#RequestParam MultiValueMap<String, String> requestParams,
#RequestHeader HttpHeaders requestHeaders,
ProxyExchange<byte[]> proxy) throws Exception {
ResponseEntity<byte[]> responseEntity = cloudGatewayService.proxyForwardToApplication(httpServletRequest, requestParams,requestHeaders, proxy);
return responseEntity;
}
(2) Source of used CloudGatewayService:
public ResponseEntity<byte[]> proxyForwardToApplication(
HttpServletRequest httpServletRequest,
MultiValueMap<String, String> requestParams,
HttpHeaders requestHeaders, ProxyExchange<byte[]> proxy) throws Exception {
String authorizationHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
String forwardUrl = "newUrl"
return proxy.sensitive("cookie")
.headers(requestHeaders)
.uri(forwardUrl).get();
}
How can I add CORS support for Spring Cloud Gateway ?
There is nothing specific to cloud MVC. Even if it is a proxy, we have to add CORS support to the web application.
As it is a spring boot project, I have added spring security as well here. Added a filter which can populate the headers did the job.
Note : add whatever required headers for your OPTION call (preflight),
Also support GET,POST, DELETE, etc calls accordingly.
The response headers can be added to the http response

Spring Security 5.3.2 OAuth 2, Resource Owner Password Credentials Flow - How to add additional HEADER parameters to authorization server uri

I'm trying to generate an access token from a custom corporate oauth 2 authorization server with Resource Owner Password Credentials Flow.
See https://www.rfc-editor.org/rfc/rfc6749#section-4.3
This server only generate an access token if receive the following parameters:
POST https://custom_corporate_server/auth/oauth/v2/token
Header
idp: 99
Body
grant_type: password
scope: my_scope
client_id: 00******-****-****-****-**********99
client_secret: 00******-****-****-****-**********99
username: my_user
password: my_password
Their configuration requires additional header custom parameter: idp - should be a numeric.
I'm using Spring Boot 2.3.0 and Spring Security 5.3.2.
I followed the link bellow to build my test example:
https://docs.spring.io/spring-security/site/docs/5.3.2.RELEASE/reference/html5/#using-the-access-token-2
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.password()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
return authorizeRequest -> {
Map<String, Object> contextAttributes = Collections.emptyMap();
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = new HashMap<>();
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return contextAttributes;
};
}
I was unable to pass this parameter in the header to the authorization server. How to do this is my main dilemma today.
Have look at this article, it explains all kinds of customizations to authorization and token requests. In your case, the section about token request extra parameters, seems to describe exactly what you need.
You could do something like this:
public class CustomRequestEntityConverter implements Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> {
private OAuth2PasswordGrantRequestEntityConverter defaultConverter;
public CustomRequestEntityConverter() {
defaultConverter = new OAuth2PasswordGrantRequestEntityConverter();
}
#Override
public RequestEntity<?> convert(OAuth2PasswordGrantRequest req) {
RequestEntity<?> entity = defaultConverter.convert(req);
MultiValueMap<String, String> params = entity.getHeaders();
params.add("idp", "99");
return new RequestEntity<>(params, entity.getHeaders(), entity.getMethod(), entity.getUrl());
}
}

Auth0 API + Spring: How to verify user identity from successful Auth0 API response

Problem
I'm trying to create an app that uses Auth0 SPA + React on the frontend to auth users without ever having to deal with passwords. Then, I'd like to secure any endpoints I create using an Auth server that I'm required to create using the Spring Framework.
Just to clarify, the flow would be
Frontend ->
Auth through Auth0 ->
Redirect to users dashboard on frontend ->
Make HTTP request to endpoint sending JWT returned from Auth0 ->
Endpoint makes request to my Auth Server sending JWT returned from Auth0 ->
Auth server either either returns 401 or user object based on JWT ->
Endpoint grabs data specific to that user from DB ->
Returns data to frontend
I've managed to get my frontend to work just fine using the Quickstart Guide that Auth0 provides but I'm having a lot of trouble figuring out how to get my Auth Service to verify the user.
I believe I've come to the conclusion that I need to create an "API" on Auth0 and grab an access token and use that to validate the JWT, which in this case is just the access token and not the JWT that my frontend contains. I've also got this part working but there doesn't seem to be a way to know who the user is. When testing this "API", after sending a valid request I am returned
{
"iss": "https://${username}.auth0.com/",
"sub": "${alphanumericCharacters}#clients",
"aud": "${ApiIdentifier}",
"iat": ${issuedAt},
"exp": ${expiresAt},
"azp": "${alphanumericCharacters}",
"gty": "client-credentials"
}
While it's good to know I'm on the right track I can't seem to figure out what to do with this response to find the user.
Expected
I expect to be able to identify a specific user after validating an access_token from my Auth Service
Code
I don't have much code to show but I'll provide what I can from my Auth Service
SecurityConfiguration.java
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.mvcMatchers("/api/validate")
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
#Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoderJwkSupport jwtDecoder = (NimbusJwtDecoderJwkSupport)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
AudienceValidator.java
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final String audience;
public AudienceValidator(String audience) {
this.audience = audience;
}
public OAuth2TokenValidatorResult validate(Jwt jwt) {
OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
if (jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(error);
}
}
ValidateController.java
#RestController
#RequestMapping("/api/validate")
public class ValidateController {
#GetMapping
public boolean validate() {
return true; // only returns if successfully authed
}
}
After reading through the docs I've found my solution.
It turns out that I don't need to create an "API" on Auth0 but instead need to use my Applications endspoint(s) from Auth0. Auth0 provides many endpoints based on your account that you can take advantage of from any of your applications (CLI, Server, Client, etc.) as long as you can:
Make an HTTP Request
Provide credentials
So the way to get a users information is explained here.
Data flow
Using my projects auth/data flow it's pretty much:
Using #auth0/auth0-spa-js on the frontend, you can grab a users access token after a successful auth by using the getTokenSilently() method.
Send up HTTP request to your Rest Service
Rest Service sends that token to your Auth Service
Auth Service sends GET request to https://myAuth0Username.auth0.com/userinfo with the Authorization: Bearer ${access_token} header. Example
If successfully authed from Auth0
Returns your users information such as "name", "email", etc.
Else
Returns a 403 Forbidden HTTP Status
Auth Service then returns user object to Rest Service
Rest Service then does necessary logic for that endpoint (DB query, another HTTP request, etc.)
Example Auth Service endpoint to validate tokens and return a user
ValidateController.java
package x.SpringTodo_Auth.Controllers;
import x.SpringTodo_Auth.Models.User;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
#RestController
#RequestMapping("/api/validate")
public class ValidateController {
#GetMapping
public Object validate() {
// Create and set the "Authorization" header before sending HTTP request
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + access_token);
HttpEntity<String> entity = new HttpEntity<>("headers", headers);
// Use the "RestTemplate" API provided by Spring to make the HTTP request
RestTemplate restTemplate = new RestTemplate();
Object user = restTemplate.exchange("https://myAuth0Username.auth0.com/userinfo", HttpMethod.POST, entity, User.class);
return user;
}
}
User.java (This is the class passed to the restTemplate.exchange(...) method as the last argument
package x.SpringTodo_Auth.Models;
public class User {
private String sub;
private String given_name;
private String family_name;
private String nickname;
private String name;
private String picture;
private String locale;
private String updated_at;
private String email;
private boolean email_verified;
// Getters/setters (or you can use Lombok)
}

Spring Boot RestTemplate exchange 400 bad request

I have a problem with Spring Boot RestTemplate exchange.
I have the following code:
#RequestMapping(path = "/add")
public #ResponseBody String addFromTo () {
String apikey = "";
String baseurl = "http://demowebshop.webshop8.dk/admin/WEBAPI/v2/orders?start=2018-10-05T20%3A49%3A41.745Z&end=2018-10-15T20%3A49%3A41.745Z&api_key=" + apikey;
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBasicAuth("", apikey);
HttpEntity<String> request = new HttpEntity<String>(" ", headers);
ResponseEntity<OrderResponse> response = restTemplate.exchange(baseurl, HttpMethod.GET, request, OrderResponse.class);
return "Some text.";
}
What I want is the equivalent of:
curl -X GET --header 'Accept: application/json' --header 'Authorization: Basic {encodedapikey}' 'http://demowebshop.webshop8.dk/admin/WEBAPI/v2/orders?start=2018-10-06T06%3A43%3A40.926Z&end=2018-10-16T06%3A43%3A40.926Z&api_key={apikey}'
I've tried using Postman with the exact same URL, and adding Basic Auth with the apikey, and an 'Accept: application/json' header, and that works fine, but when I run this code, I get the error message:
There was an unexpected error (type=Internal Server Error, status=500).
400 Bad Request
EDIT:
Pastebin link to the exception thrown by the program:
https://pastebin.com/jdYJ2nv7
In your curl request you are using an apikey and encodedapikey. Whereas in your Java code you don't. Next to that you are also passing an encoded URL as the URL to use. This will result in encoding the encoded URL again. So don't do that. Instead use a URL with placeholders and supply values for them.
#RequestMapping(path = "/add")
public #ResponseBody String addFromTo () {
String apikey = "";
String baseurl = "http://demowebshop.webshop8.dk/admin/WEBAPI/v2/orders?start={start}&end={end}&api_key={apikey}";
Map<String, Object> parameters = new HashMap<>();
parameters.put("start", "2018-10-05T20:49:41.745Z");
parameters.put("end", "2018-10-16T06:43:40.926Z");
parameters.put("apikey", apikey);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBasicAuth("", apikey);
ResponseEntity<OrderResponse> response = restTemplate.getForEntity(baseurl, OrderResponse.class, parameters);
return "Some text.";
}
The code above uses a proper parameterized URL together with a map containing values for the placeholders. Notice that those aren't encoded, as that will be handled by Spring!. Finally you can simply use the getForEntity method to get the result instead of the exchange method.
A final suggestion, Spring Boot already configures a RestTemplate which you can (re)use. You don't need to create a RestTemplate each time you need one (it is quite a heavy object to create, and after creation it is thread safe so it is enough to have a single instance).
public YourClassCOnstructor(RestTemplateBuilder builder) {
this.restTemplate = builder.basicAuthorization("", apikey).build();
}
Ofcourse you can also put this in an #Bean method and inject the specific RestTemplate into your class.
HttpHeaders.setBasicAuth(String, String) is used for username and password only, not for basic token.
If you want to use basic token try something like headers.add("Authorization", "Basic " + apiKey) instead of headers.setBasicAuth(...)

Jersey client (2.x) does not send my GET request headers

I've been debugging this for three hours, I still cannot explain why my custom headers (registered via a client request filter) are not sent.
The client is configured as such (full source here):
private WebTarget webTarget(String host, String appId, String appKey) {
return newClient(clientConfiguration(appId, appKey))
.target(host + "/rest");
}
private Configuration clientConfiguration(String appId, String appKey) {
ClientConfig config = new ClientConfig();
config.register(requestFilter(appId, appKey));
return config;
}
private ClientRequestFilter requestFilter(String appId, String appKey) {
return new VidalRequestFilter(apiCredentials(appId, appKey));
}
The filter is as follows:
public class VidalRequestFilter implements ClientRequestFilter {
private final ApiCredentials credentials;
public VidalRequestFilter(ApiCredentials credentials) {
this.credentials = credentials;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
headers.add(ACCEPT, APPLICATION_ATOM_XML_TYPE);
headers.add("app_id", credentials.getApplicationId());
headers.add("app_key", credentials.getApplicationKey());
}
}
And the call is like:
String response = webTarget
.path("api/packages")
.request()
.get()
.readEntity(String.class);
All I get is 403 forbidden, because the specific endpoint I am calling is protected (the auth is performed with the custom headers defined above).
The weirdest thing is that, while I'm debugging, I see that sun.net.www.MessageHeader is properly invoked during the request write (i.e. the instance is valued as such: sun.net.www.MessageHeader#14f9390f7 pairs: {GET /rest/api/packages HTTP/1.1: null}{Accept: application/atom+xml}{app_id: XXX}{app_key: YYY}{User-Agent: Jersey/2.22.1 (HttpUrlConnection 1.8.0_45)}{Host: ZZZ}{Connection: keep-alive}.
However, I have the confirmation that neither our API server, nor its reverse proxy received GET requests with the required auth headers (a first HEAD request seems to be OK, though).
I know for sure the credentials are good 'cause the equivalent curl command just works!
I tried the straightforward approach to set headers directly when defining the call without any success.
What am I missing?

Categories

Resources