two-legged oauth2 : how to call google drive rest API without specific API library - google-api

I have created an app in the Google Developer's Console, then created OAuth2 credentials. I have a client_id and client_secret. Now, I want to use these to obtain an access token for two-legged calls into the Google Drive API. I am using Google's oauth2 client in java:
import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.TokenResponse;
...
public void oauth2Test() {
String clientId = "...";
String clientSecret = "...";
ClientCredentialsTokenRequest request = new ClientCredentialsTokenRequest(
new NetHttpTransport(),
new JacksonFactory(),
new GenericUrl("https://accounts.google.com/o/oauth2/auth"));
request.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret));
TokenResponse response;
try {
response = request.execute();
} catch (Exception ex) {
ex.printStackTrace();
}
}
However, I get a "400 Bad Request" with message
"Required parameter is missing: response_type".
What is the correct way to obtain an access token in the two-legged request model? Note: I only have the client_id and client_secret, I do not have the full API token.
EDIT: My original question was imprecise. While I prefer to start only with client_id and client_secret, that is not necessary. It is OK to use google-specific APIs to obtain access tokens and it is OK to use GoogleCredential. What is necessary is that I am able to use whatever access token(s) are obtained from the authorization process in a generic REST call. In other words, given google app credentials, which can be {client_id,client_secret}, or a google service account key in either JSON or P12 format, how do I obtain access token(s) and how are they used in the REST API call -- do I set the Authorization header or something else?
The first answer points out that client_credential isn't supported, which I've verified. But I still need a path to get the bearer token, so that I can use it in REST calls without specific Google client API libraries. So I started with code that works, but uses the Google libraries. It requires a JSON credential file.
InputStream is = getClass().getResourceAsStream("JSONCredFile");
GoogleCredential credential = GoogleCredential.fromStream(is).createScoped(scopes);
Drive service = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName("My app")
.build();
FileList result = service.files().list().setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
By hooking up an SSLSocket proxy to the credential (details omitted), I was able to trace the outbound communication:
POST /token HTTP/1.1
Accept-Encoding: gzip
User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: oauth2.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 771
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=<lots of encoded stuff>
The reply is a gzip-encoded bearer token, which is used in the API call:
GET /drive/v3/files?fields=nextPageToken,%20files(id,%20name)&pageSize=10 HTTP/1.1
Accept-Encoding: gzip
Authorization: Bearer ya29.c.Eln_BSgrx0afa85mdMstW5jzEvM5dotWpctSXl-DE1jeO2mmu1h0FErr_EZO05YnC-B1yz30IBwOyFXoWr_wwKxlZk08R6eZldNU-EAfrQ1yNftymn_Qqc_pfg
Clearly this is the JWT profile of oauth2. But now what? Somehow I need to get the bearer token without actually making the API call through the specific library. The Google OAuth2 libraries don't seem to support this request type, at least I don't see a "JWT" flavor of TokenRequest. I can cook up the OAuth2 call directly, or create a subclass of TokenRequest that supports JWT?
Any better ideas?

Google doesn't support grant_type=client_credentials which is how you'd do 2LO with an OAuth client ID and secret.
You can use service accounts to do 2LO: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

OK, I finally figured out how to make the JWT, send the OAuth2 request, and extract the access token. For easier integration with the google OAuth2 client, I subclassed TokenRequest:
import com.google.api.client.auth.oauth2.TokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
/**
* #author jlilley
*/
public class JWTTokenRequest extends TokenRequest {
private String serviceKeyJson;
private boolean doRsa = true;
public JWTTokenRequest(HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl) {
super(transport, jsonFactory, tokenServerUrl, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
#Override
public JWTTokenRequest setTokenServerUrl(GenericUrl tokenServerUrl) {
return (JWTTokenRequest)super.setTokenServerUrl(tokenServerUrl);
}
public JWTTokenRequest setServiceKey(String json) throws Exception {
this.serviceKeyJson = json;
return this;
}
public JWTTokenRequest setServiceKey(InputStream is) throws Exception {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(is))) {
return setServiceKey(buffer.lines().collect(Collectors.joining("\n")));
}
}
#Override
public JWTTokenRequest setScopes(Collection<String> scopes) {
return (JWTTokenRequest) super.setScopes(scopes);
}
#Override
public JWTTokenRequest set(String fieldName, Object value) {
return (JWTTokenRequest) super.set(fieldName, value);
}
/**
* Create a JWT for the given project id, signed with the given RSA key.
*/
private String signJwtRsa(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Create a JWT for the given project id, signed with the given ES key.
*/
private String signJwtEs(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Finalize the JWT and set it in the assertion property of the web service call
* #throws java.io.IOException
*/
void makeAssertion() {
JsonParser parser = new JsonParser();
JsonObject jsonDoc = (JsonObject) parser.parse(serviceKeyJson);
String pkStr = jsonDoc.get("private_key").getAsString()
.replace("\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
byte[] pkBytes = Base64.getDecoder().decode(pkStr);
DateTime now = new DateTime();
JwtBuilder jwtBuilder = Jwts.builder()
.setIssuedAt(now.toDate())
.setExpiration(now.plusMinutes(60).toDate())
.setAudience(getTokenServerUrl().toString())
.setIssuer(jsonDoc.get("client_email").getAsString());
if (getScopes() != null) {
jwtBuilder = jwtBuilder.claim("scope", getScopes());
}
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkBytes);
String pkId = jsonDoc.get("private_key_id").getAsString();
jwtBuilder.setHeaderParam("kid", pkId);
jwtBuilder.setHeaderParam("typ", "JWT");
set("assertion", doRsa ? signJwtRsa(jwtBuilder, spec) : signJwtEs(jwtBuilder, spec));
}
/**
* Finalize the JWT, set it in the assertion property of the web service call, and make the token request.
*/
#Override
public TokenResponse execute() throws IOException {
makeAssertion();
return super.execute();
}
}
Give this, I can set up the token request from the service account JSON key file, execute() it, and fetch the resulting access token. Note that token renewal responsibility is up to the caller.

Related

Is there a way to use only `/login/oauth2/code/:registration_id` in spring security?

First of all, I don't want to provide a web view for social login on my mobile
The social login method for spring security is to call /oauth2/authorization/:registration_id and then /login/oauth2/code/:registration_id.
However, if you are using SDK on mobile, I don't need /oauth2/authorization/:registration_id, only need /login/oauth2/code/:registration_id.
However, it seems that only /login/oauth2/code/:registration_id is not provided.
Is there a way to use only /login/oauth2/code/:registration_id in spring security?
Your mobile app is an OAuth2 client. It should handle authentication with the authorization-server and then add Authorization header with Bearer token to the request it send to secured resource-server(s).
For authorization-server, two options:
use your "social" identity provider if it support OAuth2 but this is rarely possible, see below
put an authorization-server in front of "social" identity provider(s). This can be useful in many cases (Keycloak for instance works for all below and is in my opinion a way more mature solution than Spring's authorization-server):
you have several identity sources (Google, Facebook, etc)
you need to add role management to your users (some users need to be identified as moderators or whatever elevated privileges)
you have non OAuth2 identity sources (database or corporate LDAP for instance)
several identity sources but some of it are not issuing OpenID JWT access-tokens
Last, the resource-server (REST API) which can completely ignore login flows (which should be handled by client). All it needs is an Authorization header with a bearer token and either a JWT decoder or an introspection endpoint. See this article for configuring such a resource-server.
I configure my resource-servers to return 401 (unauthorized) when Authorization header is invalid or missing, and not 302 (redirect to login):
in my opinion, this is client responsibility to handle end-user login and also know which URIs require an Authorization (and from which issuer): if your app consumes resources from different providers (for instance Facebook plus Google Maps plus your own Spring services) it must know what access-token to associate with which request.
you can end with cases where your resource-server is exposed to several clients with different issuers. In that case, do you really want to put some code in your resource-servers to figure out to which authorization-server redirect depending on the client ID?
I don't know the side effect, but I found a way to work.
package com.example.demo.config.security;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.StreamSupport;
#Component
#RequiredArgsConstructor
public class CustomOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
private static final char PATH_DELIMITER = '/';
private final InMemoryClientRegistrationRepository clientRegistrationRepository;
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
ClientRegistration clientRegistration = resolveRegistration(request);
if (clientRegistration == null) {
return null;
}
String redirectUriAction = getAction(request, "login");
String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
return OAuth2AuthorizationRequest.authorizationCode()
.attributes((attrs) -> attrs.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()))
.clientId(clientRegistration.getClientId())
.redirectUri(redirectUriStr)
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.state(request.getParameter("state"))
.scopes(clientRegistration.getScopes())
.build();
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
return loadAuthorizationRequest(request);
}
private ClientRegistration resolveRegistration(HttpServletRequest request) {
return StreamSupport.stream(clientRegistrationRepository.spliterator(), false)
.filter(registration -> {
return Optional
.ofNullable(UriComponentsBuilder
.fromHttpUrl(registration.getRedirectUri())
.build().getPath())
.orElse("")
.equals(request.getRequestURI());
})
.findFirst()
.orElse(null);
}
private String getAction(HttpServletRequest request, String defaultAction) {
String action = request.getParameter("action");
if (action == null) {
return defaultAction;
}
return action;
}
private static String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration,
String action) {
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
// #formatter:off
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build();
// #formatter:on
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
String host = uriComponents.getHost();
uriVariables.put("baseHost", (host != null) ? host : "");
// following logic is based on HierarchicalUriComponents#toUriString()
int port = uriComponents.getPort();
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
String path = uriComponents.getPath();
if (StringUtils.hasLength(path)) {
if (path.charAt(0) != PATH_DELIMITER) {
path = PATH_DELIMITER + path;
}
}
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("action", (action != null) ? action : "");
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri()).buildAndExpand(uriVariables)
.toUriString();
}
}

Service that checks if token hasnt expired

so I have a authentication bean which provides access tokens from client credentials.
public class AuthServiceBean {
#Value("${some.url}")
private String someUrl;
#Value("${some.clientId}")
private String someClientId;
#Value("${some.secret}")
private String someSecret;
#Value("${some.username}")
private String someUsername;
#Value("${some.password}")
private String somePassword;
public AuthInfo getPrevAuth() {
return prevAuth;
}
public void setPrevAuth(AuthInfo prevAuth) {
this.prevAuth = prevAuth;
}
private AuthInfo prevAuth;
public AuthInfo getAuthInfo() throws IOException {
if (this.prevAuth != null && this.prevAuth.isNotExpired()) {
return this.prevAuth;
}
return this.Authenticate();
}
private AuthInfo Authenticate() throws IOException {
final String url = this.someUrl + "/api/oauth/v1/token";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
String clientIdSecret = this.someClientId +":"+ this.someSecret;
String authString = Base64.getEncoder().encodeToString(clientIdSecret.getBytes());
headers.add("Authorization", "Basic " + authString);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("username", this.someUsername);
map.add("password", this.somePassword);
map.add("grant_type", "password");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
String bodyString = response.getBody().toString();
ObjectMapper mapper = new ObjectMapper();
try {
AuthInfo authInfo = mapper.readValue(bodyString, AuthInfo.class);
this.prevAuth = authInfo;
return this.prevAuth;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
And now how do I need to create service which checks if that access token valid if it hasn't expired and how to use refresh token. When access token expires I could ask new token with refresh token? Would be good to get any examples.
First of all, As I see in your code, you are using password grant type, not client credentials, and because of this, you pass also user credentials (username and password) in addition to the client credentials, client id and client secret.
Anyway, the reason because all the examples you found to check expiration are using jwt tokens is because these tokens have this information coded in the token itself, so you can parse it using some kind of library like Nimbus Jose and get the "exp" claim and check directly if that date is before or after the actual date.
If the token is an opaque one (not jwt). You don't have any way to check the expiration without call the server who issued that token. Normally the server (an oauth2 server) provides and endpoint called introspect in which you pass a token and it responds indicating if this token is valid or is not, because it has expired or it is revoked etc..

Secure Spring Boot API without user authentication

I'm currently developing a few services with spring boot and some security concerns came in mind. All the exposed endpoints are public and do not require any user/password authentication. However, these services cannot be easily accessible by a caller other than our front-end application, since we must gather some user information through a form, in which a captcha performs a validation. Because of that, we need to ensure that the services are only invoked by this front-end application and that fake requests are denied.
Due to these requirements, i initially thought that making the endpoints accessible via https was enough. Notwithstanding, the possibility of replay attacks and spoofing still concerns me.
So, reading a few articles i came up with the following draft:
Please refer the client as the front-end application.
client and server should share a key-pair (public and private keys).
For every request, the following must be satisfied:
client creates a unique nonce (random number)
client generates a HMAC-SHA1 token with the shared private key
token = hmac('sha1', private_key, public_key + timestamp + nonce);
client must send the public_key, timestamp, nonce and token in header
upon receiving a request, the server checks if all the header params are present and then calculates the same hmac-sha1 token and compares with the received value from the client.
the nonce is then added to a cache manager, so that duplicated requisitions are discarded.
if any of header parameters are missing or if the calculated token is different from the one sent by the client, the requesition is also discarded.
Is this an appropriate approach? Are the benefits of such overhead worth?
These are the codes i currently have:
#Service
public class APIAuthenticationManager implements AuthenticationManager {
#Value("${security.http.api_key}")
private String apiKeyValue;
#Value("${security.http.api_key_header}")
private String apiKeyRequestHeader;
#Value("${security.valid_timestamp.thresold}")
private String timestampThresold;
#Value("${security.valid_timestamp.header}")
private String timestampHeader;
#Value("${security.nonce.header}")
private String nonce;
#Value("${security.token.header}")
private String tokenHeader;
#Value("${security.private_key}")
private String privateKey;
#Override
public Authentication authenticate(Authentication authentication) {
HttpServletRequest request = (HttpServletRequest) authentication.getPrincipal();
if (!apiKeyValue.equals(request.getParameter(apiKeyRequestHeader))) {
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
String timestamp = request.getParameter(timestampHeader);
if(timestamp == null) {
throw new BadCredentialsException("Timestamp was not found or its value is invalid.");
}
Date requestIssueDate = Util.parseDate(timestamp, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
if(requestIssueDate == null) {
throw new BadCredentialsException("Timestamp was not found or its value is invalid.");
}
long expired = System.currentTimeMillis() - Integer.valueOf( timestampThresold );
if (requestIssueDate.getTime() > expired) {
throw new BadCredentialsException("Timestamp was not found or its value is invalid.");
}
// HMAC('SHA1', 'API_KEY', 'TOKEN GENERATED IN CLIENT');
// TOKEN GENERATED IN CLIENT = HMAC('SHA1', 'API_KEY', 'SECRET_KEY + TIMESTAMP + NONCE');
String tokenFromClient = request.getParameter(tokenHeader);
String calculatedToken = HMACSignatureUtil.calculateHMAC(privateKey, apiKeyValue + timestamp + nonce);
if(!tokenFromClient.equals(calculatedToken)) {
throw new BadCredentialsException("Invalid token.");
}
authentication.setAuthenticated(true);
return authentication;
}
This is the ConfigurerAdapter
#Autowired
private APIAuthenticationManager apiAuthenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
APIKeyAuthFilter filter = new APIKeyAuthFilter();
filter.setAuthenticationManager( apiAuthenticationManager );
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
http.requiresChannel()
.anyRequest(). requiresSecure();
}

feign client - retrieving jwt token from a service and setting it automatically in feign client

I'm using feign client to call other services.
Service A need to contact Service B and has to be authenticated via authentication service.
If I wasn't using feign, I would just use resttemplate calling first the authentication service.. get the token, add it to the header of the msg I want to send to service B.
Is it possible to configure to feign an endpoint that from there he gets the token so it would be done automatically?
Considering you have prepeared JWT authroization and authentication in the authentication service, i can give my example like this:
First feign client using OpenFeign (the latest feign implementation of spring)
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
// Must give a url to NOT use load balancer (we have a gateway)
// Otherwise it will throw "did you forget load balancer?" error
#FeignClient(url = "http://localhost:<port>/", name = "UserClient")
public interface UserClient
{
#PostMapping(path = "login", consumes = "application/json")
ResponseEntity<String> login(#RequestBody String user);
#GetMapping(path = "hello")
ResponseEntity<String> sayHello();
}
Then get the jwt token like this: (This code can be in some mvc project)
String username = someuser
String password = somepassword
// I wasn't able to create a json but the below data would work with spring security
String user = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}";
ResponseEntity<String> responseEntity = userClient.login(user);
// your token could be like "<username> - <token>" kind of structure
String token = responseEntity.getBody();
Then make an authenticated request with openfeign, this code can be in mvc project or something
// If you put the token in session
String bearer = (String) req.getSession().getAttribute("Bearer");
UserClient helloClient = Feign.builder().client(new Client.Default((SSLSocketFactory) SSLSocketFactory.getDefault(), null)).contract(new SpringMvcContract()).requestInterceptor(new RequestInterceptor()
{
#Override
public void apply(RequestTemplate template)
{
System.err.println("This is adding jwt header to resttemplate of the feign");
template.header("Authorization", "Bearer " + bearer);
}
}).decoder(new Decoder()
{
#Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException
{
// If you have returned responseentity, feign client must be able to understand it
return new ResponseEntity<>(response.body().toString(), HttpStatus.resolve(response.status()));
}
}).target(UserClient.class, "http://localhost:4441/hello/");
// My responsebody is a simple string but it could be anything you want, as long as you have the decoder
String response = helloClient.sayHello().getBody();
Feign clients does not have the ability to dynamically set headers, especially from jwt tokens because those tokens are coming from either another request or in session. That is why i use builder to set a dynamic header. If you use BasicAuthRequestInterceptor, you can create a #Bean of BasicAuthRequestInterceptor and set a static username and password inside it.

Standalone SpringBoot app with OAuth2 authentication

I am working on creating an app using springboot which would consume an API which has OAuth2 authentication. Post successful getting the Bearer code I would be calling another API which would actually give me data for further processing. I have custom OAuth url, authorization code, username, password, secret key, api key. When I searched on internet, none of the example were usign all of these[only secret key, authorization code and api key was getting used.]. Do I need to use username and password as well?
I tried below code [and few other things]. But not able to get through this.
<code>
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Component
public class ApiConsumer {
#Autowired
private RestTemplate template;
#Value("${oauth.api}")
String url;
#Value("${oauth.oAuth.url}")
String oAuthUrl;
#Value("${oauth.user}")
String username;
#Value("${oauth.password}")
String password;
#Value("${oauth.apikey}")
String apiKey;
#Value("${oauth.secretkey}")
String apiSecret;
public String postData() {
log.info("Call API");
try {
String response = consumeApi();
if (response.equals("200")) {
log.info("posting data to another api");
// CALL another API HERE for actual data with bearer code
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private String consumeApi() throws Exception {
String authorizationHeader = "Basic "
+ DatatypeConverter.printBase64Binary((apiKey + ":" + apiSecret).getBytes());
// setting up the HTTP Basic Authentication header value
HttpHeaders requestHeaders = new HttpHeaders();
// set up HTTP Basic Authentication Header
requestHeaders.add("Authorization", authorizationHeader);
requestHeaders.add("Accept", MediaType.APPLICATION_FORM_URLENCODED_VALUE);
requestHeaders.add("response_type", "code");
// request entity is created with request headers
HttpEntity<String> request = new HttpEntity<String>(requestHeaders);
template.getInterceptors().add(new BasicAuthorizationInterceptor(username, password));
ResponseEntity<String> result = null;
try {
result = template.exchange(oAuthUrl, HttpMethod.POST, request, String.class);
log.info( result.getBody());
if (result.getStatusCode() == HttpStatus.OK) {
transformData(result.getBody());
}
if (result.getStatusCode() != HttpStatus.REQUEST_TIMEOUT) {
throw new Exception("Api taking too long to respond! ");
}
}
catch (Exception e) {
log.error("Api taking too long to respond!");
}
return "";
}
private void transformData(String body) throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
List<HeapEntity> heapEntityList = Arrays.asList(mapper.readValue(body, HeapEntity[].class));
if (heapEntityList != null && heapEntityList.size() > 0) {
heapEntityList.forEach(i -> i.getPhoneNumber().replaceAll("-", ""));
}
log.debug("Size of list is :: " + heapEntityList.size());
heapEntityList.add(null);
}
}
</code>
Unfortunately, I cannot give a direct answer to your question, because it is not clear from it which grant type you are trying to use, and this will determine the answer to the question whether you need to use a username and password or not.
I advise you to familiarize yourself with the Section 4 of RFC 6749, in which you will find information on all grant types supported by the standard, and the request parameters they require.
Examples for the Password grant type:
If you need to use the RestTemplate, you can do something like this:
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/x-www-form-urlencoded");
headers.set("Authorization", "Basic " + Base64.getUrlEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()));
String body = String.format("grant_type=password&username=%s&password=%s", username, password);
String json = restTemplate.postForObject(tokenUrl, new HttpEntity<>(body, headers), String.class);
Note that the response is a json object containing a token, not the token itself.
Or you can simply use the more appropriate for your purpose OAuth2RestTemplate:
#Bean
public OAuth2RestTemplate oAuth2RestTemplate() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setClientAuthenticationScheme(AuthenticationScheme.form);
resource.setAccessTokenUri("tokenUrl");
resource.setClientId("clientId");
resource.setClientSecret("clientSecret");
resource.setUsername("username");
resource.setPassword("password");
return new OAuth2RestTemplate(resource);
}
Do not forget to add #EnableOAuth2Client to one of your configuration classes.

Resources