400 Error Code while using RestTemplate for a Rest API - java

I have a REST API to call and I have written the client using rest template. When executing, I am getting 400 status code. The same REST API is working fine when using POSTMAN. Below are the code snippets for API and caller. Do let me know if anyone catches anything.
REST API for POST method-
#ApiOperation(value = "Download repository as zip")
#ApiResponses({#ApiResponse(code = 200, message = ""), #ApiResponse(code = 400, message = "")})
#PostMapping(value = "/download", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public ResponseEntity<StreamingResponseBody> downloadRepository(
#RequestBody #Validated final RepositoriesRequest repositoriesRequest) {
final Situation situation = this.situationsService.getSituationId(repositoriesRequest);
if (isNull(situation)) {
return ResponseEntity.notFound().build();
} else {
final ExtractionRequest extractionRequest = new ExtractionRequest(repositoriesRequest.getType(), situation,
repositoriesRequest.getDatabase());
if (!this.validateRequest(extractionRequest)) {
return ResponseEntity.badRequest().build();
}
final ExtractionResponse response = this.extractService.extractRepository(extractionRequest);
if (null == response) {
return ResponseEntity.notFound().build();
}
final InputStream inputStream = this.extractService.getFileFromS3(response.getRepositoryPath());
if (null == inputStream) {
return ResponseEntity.noContent().build();
}
final StreamingResponseBody bodyWriter = this.bodyWriter(inputStream);
return ResponseEntity.ok()
.header("Content-Type", "application/zip")
.header(CONTENT_DISPOSITION, "attachment; filename=\"repository-" + situation.getId() + ".zip\"")
.body(bodyWriter);
}
}
REST CLIENT using Rest Template with auth token and request body as input -
HttpEntity<MultiValueMap<String, Object>> buildLoadRepoRequest(
final SimulationContext context,
final List<String> tablesName,
final String simulationId,
final Integer offset) {
final Token token = this.authenticateOkoye(simulationId, offset);
LOGGER.info("Token Run: {}", token.getAccessToken());
final String database = this.getDatabaseForEnvironment();
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(APPLICATION_JSON_UTF8);
httpHeaders.set(AUTHORIZATION, "Bearer " + token.getAccessToken());
final MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("database", database);
body.add("monthlyClosingMonth", context.getMonthlyClosingDate());
body.add("repositorySnapshot", context.getRepository());
body.add("situationId", context.getSituationId());
body.add("tableNames", tablesName);
body.add("type", context.getRunType());
return new HttpEntity<>(body, httpHeaders);
}
Exception Handler -
#Override
#ExceptionHandler(HttpClientErrorException.class)
public void loadRepository(
final SimulationContext context,
final List<String> tablesName,
final String simulationId,
final Integer offset,
final Path repositoryPath) throws IOException {
LOGGER.info("[{}] [{}] repository tablesName: {}", simulationId, offset, tablesName);
this.restTemplate.setRequestFactory(this.getClientHttpRequestFactory());
final ClientHttpResponse response = this.restTemplate.postForObject(
this.repositoriesUrl,
this.buildLoadRepoRequest(context, tablesName, simulationId, offset),
ClientHttpResponse.class);
if (response != null && HttpStatus.OK == response.getStatusCode()) {
LOGGER.info(
"response status on simulation : {} - Context: {} - status: {}",
simulationId,
offset,
response.getStatusCode());
//this.helper.copy(response.getBody(), repositoryPath);
} else if (response != null && HttpStatus.NO_CONTENT != response.getStatusCode()) {
throw new JarvisException(
"Can't retrieve RWA repository on simulation " + simulationId + " Context:" + offset);
}
}
We have been looking into this issue since yesterday and still don't have any clue. So far we have tried postForEntity, exchange, changing the headers to proper setter methods and tried passing the parameters as an object also. None of them worked.
I have a strong feeling about something being wrong at header level while calling the API.

Did you try to use httpHeaders.setContentType(MediaType.APPLICATION_JSON)
Or add consumes to #PostMapping annotation with APPLICATION_JSON_UTF8 value

Related

Rest Controller method that returns a token by calling two service methods that requires rest template calls to an external api

I want to return a token response in my controller, my rest controller method is calling two services which are implementing Rest Template post method to call external apis.
My tokenservice layer requires a parameter "code" to be passed into it.
When I try to write my rest controller method to implement my codeservice and only return string "code" back, the code is returned from the external Api.
But when I try to run the method implementing get code and return token in a single rest endpoint I get errors :
Stack trace:
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:785)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
Do I need to pass in both the Body(x-www-form-urlencoded and raw body) in a single call?
//RETRIEVE CODE SERVICE //
#Override
public String retrieveCode(String client_id, MUser mUser) {
String bUrl = "xxx";
String url = UriComponentsBuilder.fromUriString(bUrl)
.queryParam("clientId", client_id)
.build()
.toUriString();
HttpEntity<MUser> httpEntity = new HttpEntity<>(mUser, getHttpHeaders());
String code = "";
try {
ResponseEntity<MR> responseEntity = this.restTemplate.postForEntity(url, httpEntity, MR.class);
code = responseEntity.getBody().getCode();
} catch(HttpClientErrorException | HttpServerErrorException httpException) {
log.error("Could not retrieve code: {}" httpException.getMessage());
}
return code;
}
protected HttpHeaders getHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
return headers;
}
//TOKEN SERVICE //
#Override
public Token retrieveToken(String code, boolean c1, boolean c2) {
String url = UriComponentsBuilder.fromUriString(authProperties.getTokenUrl())
.build().toUriString();
HttpEntity<MultiValueMap<String, String>> entity = getHttpEntity(code, p1, p2);
ResponseEntity<Token> responseEntity = this.restTemplate.postForEntity(url, entity, Token.class);
return responseREntity.getBody();
}
protected Http<MultiValueMap<String, String>> getHttpEntity(String code, boolean c1, boolean c2Refresh) {
MultiValueMao<String, String> map = new LinkedMultiValueMap();
if(c1 || c2Refresh) {
map.add(CLIENT_ID, this.authTokenServiceConfig.getC1ClientId());
map.add(CLIENT_SECRET, this.authTokenServiceConfig.getC1ClientSecret());
} else {
map.add(CLEINT_ID, this.authTokenServiceConfig.getFClientId());
map.add(CLIENT_SECRET, this.authTokenServiceConfig.getFClientSecret());
}
map.add(CODE, code);
map.add(GRANT_TYPE, AUTHORIZATION_CODE);
map.add(REDIRECT_URI, authProperties.getRegisteredRedirectURL())
return new HttpEntity<>(map, getHttpHeaders());
}
protected HttpHeaders getHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
return headers;
}
// REST CONTROLLER METHOD //
#PostMapping(value = "/mauth")
public Token mAuthorize(#RequestParam(required= true) String clientId, #RequestBody MUser mUser) {
String code = authService.retrieveCode(clientId, mUser);
Token token = authTokenService.retrieveToken(code, false, false); // when I comment this line out and return just the String code It works properly
return token;
}
enter image description here

How to terminate request flow at spring cloud api gateway and redirecting to different URL route Path

I have implemented the following microservice applications.
Request flow.
Ui Client(http://localhost:8080) ------> spring cloud
gateway(http://localhost:8081) ------> user
microservice(http://localhost:8602)(api
endpoint=/api/v1/users/bulkUpload)
I am sending an Ajax request to user microservice through the spring cloud gateway service.
Ajax request contains refresh token as a cookie value.
$.ajax({
type: "POST",
enctype: 'multipart/form-data',
url: "http://localhost:8081/api/v1/users/bulkUpload",
xhrFields: {
withCredentials: true
},
data: newData,
processData: false,
contentType: false,
crossDomain: false,
cache: false,
timeout: 600000,
success: function (data) {
......
}
but if the refresh token is not available in Ajax request I want to terminate the request at API gateway level and i want to redirect the user to the Ui client logout page(http://localhost:8080/Logout).
for that, I have implemented a spring cloud gateway filter as follows.
#Component
public class AccessTokenCheckingGlobalFilterPre extends AbstractGatewayFilterFactory<AccessTokenCheckingGlobalFilterPre.Config> {
#Value("${security.oauth2.client.client-id}")
private String client_id;
#Value("${security.oauth2.client.client-secret}")
private String client_secret;
public AccessTokenCheckingGlobalFilterPre() {
super(AccessTokenCheckingGlobalFilterPre.Config.class);
}
#Override
public GatewayFilter apply(AccessTokenCheckingGlobalFilterPre.Config config) {
return (exchange, chain) -> {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
if(route!=null && request.getPath().toString().contains("oauth/token")) {
return chain.filter(exchange.mutate().request(request).response(response).build());
}else {
MultiValueMap<String, HttpCookie> cookies = request.getCookies();
List<HttpCookie> accessTokenList = cookies.get("access_token");
List<HttpCookie> refreshTokenList = cookies.get("refresh_token");
HttpHeaders heraders = request.getHeaders();
String access_token="";
String refresh_token = "";
if(accessTokenList != null) {
access_token = accessTokenList.get(0).getValue();
}
if(refreshTokenList != null) {
refresh_token = refreshTokenList.get(0).getValue();
}
if(access_token.isEmpty() && !refresh_token.isEmpty()) {
RestTemplate restTemplate = new RestTemplate();
String credentials = client_id + ":" + client_secret;
String encodedCredentials = new String(Base64.encode(credentials.getBytes()));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Basic " + encodedCredentials);
HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
String access_token_url = "http://localhost:8602/oauth/token";
access_token_url += "?grant_type=refresh_token";
access_token_url += "&refresh_token=" + refresh_token;
ResponseEntity<String> aouthResponse = restTemplate.exchange(access_token_url, HttpMethod.POST, requestEntity, String.class);
String responseJson = access_token = aouthResponse.getBody();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(responseJson);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
String newRefreshToken = node.path("refresh_token").asText();
String newAccessToken = node.path("access_token").asText();
int expiresIn = Integer.parseInt(node.path("expires_in").asText());
int refreshTokenExpireTime = Integer.parseInt(node.path("refreshTokenExpires_In").asText());
ResponseCookie accessTokenCookie = ResponseCookie.from("access_token", newAccessToken)
.path("/")
.maxAge(expiresIn)
.build();
ResponseCookie refreshTokenCookie = ResponseCookie.from("refresh_token", newRefreshToken)
.path("/")
.maxAge(refreshTokenExpireTime)
.build();
response.addCookie(refreshTokenCookie);
response.addCookie(accessTokenCookie);
access_token = newAccessToken;
}else if(refresh_token.isEmpty()){
//request.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
//response.setComplete();
return chain.filter(exchange.mutate().request(request).response(response).build());
}
final String value = access_token;
request = request.mutate()
.headers(httpHeaders -> httpHeaders.add("Authorization", "Bearer " + value))
.build();
}
return chain.filter(exchange.mutate().request(request).response(response).build());
};
}
public static class Config {
}
}
this logic has been implemented to check whether refresh token present in the request.
unless it is redirecting Ui client logout page(http://localhost:8080/Logout).
}else if(refresh_token.isEmpty()){
//request.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
//response.setComplete();
return chain.filter(exchange.mutate().request(request).response(response).build());
}
but still, no request termination happening at the API gateway level and the request still is forwarding to user microservice.
How to terminate the request flow at apigateway filter and redirect back to ui client logout page.
Instead of
}else if(refresh_token.isEmpty()){
//request.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
//response.setComplete();
return chain.filter(exchange.mutate().request(request).response(response).build());
}
to redirect the user, use
}else if(refresh_token.isEmpty()){
response.setStatusCode(HttpStatus.FOUND); //302
response
.getHeaders()
.set("Location", "/logout");
return response.setComplete();
}

How to assert "Content-Type" set by SpringFramework's org.springframework.http.ResponseEntity class?

I have one GET Rest-endpoint in my sample app which returns some data based on criteria it can also return null if no data is available with HTTP status as 204 else 200 OK if data is available.
#GetMapping("/homepage")
public ResponseEntity getHomePageCollections(#RequestHeader(value = HEADER_APP_TOKEN) String headerAppToken) {
CollectionObject homepageCollections = null;
String errorMessage = null;
HttpStatus httpStatus;
try {
homepageCollections = collectionService.getHomePageCollections();
if (nonNull(homepageCollections)) {
httpStatus = HttpStatus.OK;
LOGGER.info("{} Response Status from CollectionController -- getHomePageCollections !! {}", TRANSACTION_SUCCESS_CODE, TRANSACTION_SUCCESS);
} else {
httpStatus = HttpStatus.NO_CONTENT;
LOGGER.info("{} Response Status from CollectionController -- getHomePageCollections !! {}", NO_CONTENT_CODE, NO_CONTENT);
}
} // catch logic
return ResponseEntity.status(httpStatus).contentType(MediaType.APPLICATION_JSON).body(httpStatus == HttpStatus.OK || httpStatus == HttpStatus.NO_CONTENT ? homepageCollections : errorMessage);
}
I have 2 questions, first is how to assert the content type is
set by the controller in my unit test
Unit Test
#Test
public void testGetHomePageCollection() {
when(collectionService.getHomePageCollections()).thenReturn(null);
ResponseEntity responseEntity = collectionController.getHomePageCollections(HEADER_APP_TOKEN);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
}
When the homepageCollections is null spring auto-sets the content type as octet-stream, is there a reason behind it?
The ContentType is present in the headers of the response, so you can test the same by accessing it as below:
#Test
public void testGetHomePageCollection() {
when(collectionService.getHomePageCollections()).thenReturn(null);
ResponseEntity responseEntity = collectionController.getHomePageCollections(HEADER_APP_TOKEN);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
assertThat(responseEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
}

SpringBoot how to Send response to other URL

I have the following code:
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
public ResponseEntity<?> api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
String response="{SOME_JSON}";
URI callbackURL = new URI("http://otherAPIEnv/api2");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(callbackURL);
return new ResponseEntity<String>(response,httpHeaders, HttpStatus.OK);
}
I tried the above code, but when I hit the api1 through my curl I get the response on the same machine, but I want the response to be redirected to api2 at otherAPIEnv machine.
Could someone please suggest how to achieve this kind of request and response?
When you send a request to a URL it should respond to the same otherwise client will be in waiting for it until it times out.
So, the approach should be different in this scenario.
First, in your main rest API you have to send a response code to release the client.
Then, in the API method you have to call another method asynchronously which calls api2 and performs the desired operation.
Here is a simple example.
#Autowired
API2Caller api2Caller;
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
#ResponseStatus(HttpStatus.ACCEPTED)
public void api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
api2Caller.callApi2(requestBody);
}
and the APICaller should look like following
#Component
public class API2Caller {
#Async
public SomeResultPojo callApi2() {
// use RestTemplate to call the api2
return restTemplate.postForObject("http://otherAPIEnv/api2", request, SomeResultPojo.class);
}
}
But you can choose your most comfortable way to perform asynchronous operation.
Look like a job for redirect.
String redirectMe() {
return "redirect:http://otherAPIEnv/api2"
}
As for the curl. You have POST mapping of the method so be sure to try it with curl -X POST... or change it to GET.
This the more modular and more generic way to do such kind of things:
public #ResponseBody ClientResponse updateDocStatus(MyRequest myRequest) {
ClientResponse clientResponse = new ClientResponse(CTConstants.FAILURE);
try {
HttpHeaders headers = prepareHeaders();
ClientRequest request = prepareRequestData(myRequest);
logger.info("cpa request is " + new Gson().toJson(request));
HttpEntity<ClientRequest> entity = new HttpEntity<ClientRequest>(request, headers);
String uri = cpaBaseUrl + updateDocUrl ;
ClientResponse serviceResponse = Utilities.sendHTTPRequest(uri, entity);
clientResponse = serviceResponse;
if (serviceResponse != null) {
if (CTConstants.SUCCESS.equalsIgnoreCase(serviceResponse.getStatus())) {
clientResponse.setStatus(CTConstants.SUCCESS);
clientResponse.setMessage(" update success.");
}
}
} catch (Exception e) {
logger.error("exception occurred ", e);
clientResponse.setStatus(CTConstants.ERROR);
clientResponse.setMessage(e.getMessage());
}
return clientResponse;
}
public static ClientResponse sendHTTPRequest(String uri, HttpEntity<ClientRequest> entity) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory());
SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setReadTimeout(CTConstants.SERVICE_TIMEOUT);
rf.setConnectTimeout(CTConstants.SERVICE_TIMEOUT);
ParameterizedTypeReference<ClientResponse> ptr = new ParameterizedTypeReference<ClientResponse>() {
};
ResponseEntity<ClientResponse> postForObject = restTemplate.exchange(uri, HttpMethod.POST, entity, ptr);
return postForObject.getBody();
}
You need to use redirect and modify the return type of your method
public String api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException {
return "redirect:http://otherAPIEnv/api2";
}

Spring Security OAuth 1.0 flow - Consumer verification

I have an external partner that uses OAuth 1.0 to protect some resources. I need to access this resources and I would like to do this using Spring Boot and Spring Security OAuth. As I don't want to use XML configuration, I already searched for a way to set up everything via Java configuration. I found this thread that provided an example of how to do this. But serveral things regarding the OAuth 1.0 flow are not clear for me.
My partner provides four endpoints for OAuth: an endpoint that provides a consumer token, a request_token endpoint, an authorization endpoint and an access_token endpoint. With my current setup (shown below) I can get a request token and the authorization endpoint gets called. However, the authorization endpoint does not ask for confirmation, but expects as URL parameters an email and a password and, after checking the credentials, returns the following:
oauth_verifier=a02ebdc5433242e2b6e582e17b84e313
And this is where the OAuth flow gets stuck.
After reading some articles about OAuth 1.0 the usual flow is this:
get consumer token / key
get oauth token using the consumer token via request_token endpoint
redirect to authorization URL and ask the user for confirmation
redirect to consumer with verifier token
user verifier token and oauth token to get access token via access_token endpoint
First of all: steps 3 and 4 are not clear to me. I've found the Spring Security OAuth examples, but it wasn't clear to me how, after confirming the access, the user / verifier token get send back to the consumer. Could someone please explain how this is done?
Second: Given that my partners endpoint does not ask for confirmation but returns an oauth verifier right away, how can I use Spring Security OAuth with this setup? I was thinking about implementing my own authorization endpoint that calls the authorziation endpoint of my partner and then somehow makes the verifier known to my consumer, but I'm not sure how to do the latter part.
Here is the code so far (with help for the thread mentioned above; the ConsumerTokenDto has been left out as it is trivial):
Application
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Endpoint
#RestController
public class Endpoint {
#Autowired
private OAuthRestTemplate oAuthRestTemplate;
private String url = "https://....";
#RequestMapping("/public/v1/meters")
public String getMeters() {
try {
return oAuthRestTemplate.getForObject(URI.create(url), String.class);
} catch (Exception e) {
LOG.error("Exception", e);
return "";
}
}
}
OAuth configuration
#Configuration
#EnableWebSecurity
public class OAuthConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestTemplateBuilder restTemplateBuilder;
private ConsumerTokenDto consumerTokenDto;
private static final String ID = "meters";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
http.addFilterAfter(this.oauthConsumerContextFilter(), SwitchUserFilter.class);
http.addFilterAfter(this.oauthConsumerProcessingFilter(), OAuthConsumerContextFilterImpl.class);
}
private OAuthConsumerContextFilter oauthConsumerContextFilter() {
OAuthConsumerContextFilter filter = new OAuthConsumerContextFilter();
filter.setConsumerSupport(this.consumerSupport());
return filter;
}
private OAuthConsumerProcessingFilter oauthConsumerProcessingFilter() {
OAuthConsumerProcessingFilter filter = new OAuthConsumerProcessingFilter();
filter.setProtectedResourceDetailsService(this.prds());
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> map = new LinkedHashMap<>();
// one entry per oauth:url element in xml
map.put(
new AntPathRequestMatcher("/public/v1/**", null),
Collections.singletonList(new SecurityConfig(ID)));
filter.setObjectDefinitionSource(new DefaultFilterInvocationSecurityMetadataSource(map));
return filter;
}
#Bean
OAuthConsumerSupport consumerSupport() {
CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
consumerSupport.setProtectedResourceDetailsService(prds());
return consumerSupport;
}
#Bean
ProtectedResourceDetailsService prds() {
InMemoryProtectedResourceDetailsService service = new InMemoryProtectedResourceDetailsService();
Map<String, ProtectedResourceDetails> store = new HashMap<>();
store.put(ID, prd());
service.setResourceDetailsStore(store);
return service;
}
ProtectedResourceDetails prd() {
ConsumerTokenDto consumerToken = getConsumerToken();
BaseProtectedResourceDetails resourceDetails = new BaseProtectedResourceDetails();
resourceDetails.setId(ID);
resourceDetails.setConsumerKey(consumerToken.getKey());
resourceDetails.setSharedSecret(new SharedConsumerSecretImpl(consumerToken.getSecret()));
resourceDetails.setRequestTokenURL("https://.../request_token");
// the authorization URL does not prompt for confirmation but immediately returns an OAuth verifier
resourceDetails.setUserAuthorizationURL(
"https://.../authorize?email=mail&password=pw");
resourceDetails.setAccessTokenURL("https://.../access_token");
resourceDetails.setSignatureMethod(HMAC_SHA1SignatureMethod.SIGNATURE_NAME);
return resourceDetails;
}
// get consumer token from provider
private ConsumerTokenDto getConsumerToken() {
if (consumerTokenDto == null) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("client", "Client");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(1000).setReadTimeout(1000).build();
restTemplate.getInterceptors().add(interceptor);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
ResponseEntity<ConsumerTokenDto> response = restTemplate
.exchange("https://.../consumer_token", HttpMethod.POST, request,
ConsumerTokenDto.class);
consumerTokenDto = response.getBody();
}
return consumerTokenDto;
}
// create oauth rest template
#Bean
public OAuthRestTemplate oAuthRestTemplate() {
OAuthRestTemplate oAuthRestTemplate = new OAuthRestTemplate(prd());
oAuthRestTemplate.getInterceptors().add(interceptor);
return oAuthRestTemplate;
}
}
I think I've found a solution. The trick is to implement my own OAuthConsumerContextFilter and replace the redirect call with a direct call to the authorization endpoint. I've commented the interesting parts below (starting with //!!!!).
CustomOAuthConsumerContextFilter
public class CustomOAuthConsumerContextFilter extends OAuthConsumerContextFilter {
private static final Logger LOG = LoggerFactory.getLogger(CustomOAuthConsumerContextFilter.class);
private RestTemplateBuilder restTemplateBuilder;
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
OAuthSecurityContextImpl context = new OAuthSecurityContextImpl();
context.setDetails(request);
Map<String, OAuthConsumerToken> rememberedTokens =
getRememberMeServices().loadRememberedTokens(request, response);
Map<String, OAuthConsumerToken> accessTokens = new TreeMap<>();
Map<String, OAuthConsumerToken> requestTokens = new TreeMap<>();
if (rememberedTokens != null) {
for (Map.Entry<String, OAuthConsumerToken> tokenEntry : rememberedTokens.entrySet()) {
OAuthConsumerToken token = tokenEntry.getValue();
if (token != null) {
if (token.isAccessToken()) {
accessTokens.put(tokenEntry.getKey(), token);
} else {
requestTokens.put(tokenEntry.getKey(), token);
}
}
}
}
context.setAccessTokens(accessTokens);
OAuthSecurityContextHolder.setContext(context);
if (LOG.isDebugEnabled()) {
LOG.debug("Storing access tokens in request attribute '" + getAccessTokensRequestAttribute() + "'.");
}
try {
try {
request.setAttribute(getAccessTokensRequestAttribute(), new ArrayList<>(accessTokens.values()));
chain.doFilter(request, response);
} catch (Exception e) {
try {
ProtectedResourceDetails resourceThatNeedsAuthorization = checkForResourceThatNeedsAuthorization(e);
String neededResourceId = resourceThatNeedsAuthorization.getId();
//!!!! store reference to verifier here, outside of loop
String verifier = null;
while (!accessTokens.containsKey(neededResourceId)) {
OAuthConsumerToken token = requestTokens.remove(neededResourceId);
if (token == null) {
token = getTokenServices().getToken(neededResourceId);
}
// if the token is null OR
// if there is NO access token and (we're not using 1.0a or the verifier is not null)
if (token == null || (!token.isAccessToken() &&
(!resourceThatNeedsAuthorization.isUse10a() || verifier == null))) {
//no token associated with the resource, start the oauth flow.
//if there's a request token, but no verifier, we'll assume that a previous oauth request failed and we need to get a new request token.
if (LOG.isDebugEnabled()) {
LOG.debug("Obtaining request token for resource: " + neededResourceId);
}
//obtain authorization.
String callbackURL = response.encodeRedirectURL(getCallbackURL(request));
token = getConsumerSupport().getUnauthorizedRequestToken(neededResourceId, callbackURL);
if (LOG.isDebugEnabled()) {
LOG.debug("Request token obtained for resource " + neededResourceId + ": " + token);
}
//okay, we've got a request token, now we need to authorize it.
requestTokens.put(neededResourceId, token);
getTokenServices().storeToken(neededResourceId, token);
String redirect =
getUserAuthorizationRedirectURL(resourceThatNeedsAuthorization, token, callbackURL);
if (LOG.isDebugEnabled()) {
LOG.debug("Redirecting request to " + redirect +
" for user authorization of the request token for resource " +
neededResourceId + ".");
}
request.setAttribute(
"org.springframework.security.oauth.consumer.AccessTokenRequiredException", e);
// this.redirectStrategy.sendRedirect(request, response, redirect);
//!!!! get the verifier from the authorization URL
verifier = this.getVerifier(redirect);
//!!!! start next iteration of loop -> now we have the verifier, so the else statement below shoud get executed and an access token retrieved
continue;
} else if (!token.isAccessToken()) {
//we have a presumably authorized request token, let's try to get an access token with it.
if (LOG.isDebugEnabled()) {
LOG.debug("Obtaining access token for resource: " + neededResourceId);
}
//authorize the request token and store it.
try {
token = getConsumerSupport().getAccessToken(token, verifier);
} finally {
getTokenServices().removeToken(neededResourceId);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Access token " + token + " obtained for resource " + neededResourceId +
". Now storing and using.");
}
getTokenServices().storeToken(neededResourceId, token);
}
accessTokens.put(neededResourceId, token);
try {
//try again
if (!response.isCommitted()) {
request.setAttribute(getAccessTokensRequestAttribute(),
new ArrayList<>(accessTokens.values()));
chain.doFilter(request, response);
} else {
//dang. what do we do now?
throw new IllegalStateException(
"Unable to reprocess filter chain with needed OAuth2 resources because the response is already committed.");
}
} catch (Exception e1) {
resourceThatNeedsAuthorization = checkForResourceThatNeedsAuthorization(e1);
neededResourceId = resourceThatNeedsAuthorization.getId();
}
}
} catch (OAuthRequestFailedException eo) {
fail(request, response, eo);
} catch (Exception ex) {
Throwable[] causeChain = getThrowableAnalyzer().determineCauseChain(ex);
OAuthRequestFailedException rfe = (OAuthRequestFailedException) getThrowableAnalyzer()
.getFirstThrowableOfType(OAuthRequestFailedException.class, causeChain);
if (rfe != null) {
fail(request, response, rfe);
} else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
} else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. These are not expected to happen
throw new RuntimeException(ex);
}
}
}
} finally {
OAuthSecurityContextHolder.setContext(null);
HashMap<String, OAuthConsumerToken> tokensToRemember = new HashMap<>();
tokensToRemember.putAll(requestTokens);
tokensToRemember.putAll(accessTokens);
getRememberMeServices().rememberTokens(tokensToRemember, request, response);
}
}
private String getVerifier(String authorizationURL) {
HttpEntity request = HttpEntity.EMPTY;
RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(1000).setReadTimeout(1000).build();
ResponseEntity<String> response =
restTemplate.exchange(authorizationURL, HttpMethod.GET, request, String.class);
//!!!! extract verifier from response
String verifier = response.getBody().split("=")[1];
return verifier;
}
void setRestTemplateBuilder(RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder;
}
}

Categories

Resources