How to use RestTemplate with Basic Auth - spring

How do you configure RestTemplate from Spring 4.0.3.RELEASE with Apache httpclient 4.3.2? I've followed the code from SO here, and here, and even from Apache here, and it seems pretty straightforward, yet it has never worked for me. I can verify that the Authorization header is correctly sent when I use curl and postman, but the Authorization header is never sent with the following code:
public RestTemplate createBasicAuthTemplate(String username, String password) {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
HttpClient httpClient = HttpClientBuilder.create()
.setDefaultCredentialsProvider(credentialsProvider)
.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate template = new RestTemplate(requestFactory);
return template;
}
and the code is called like so:
RestTemplate basicAuth = createBasicAuthTemplate("user#app.com", "password");
ResponseEntity<String> response = basicAuth.getForEntity(url, String.class);
So the questions are: How do you configure RestTemplate from Spring 4.0.3.RELEASE with Apache httpclient 4.3.2? Are there other pieces that the above code is missing? In the above code is the RestTemplate using the correct method?

The astute reader may have noticed that the Authorization header is never sent, and realized the problem. You just have to know that it is a standard protocol to send an unauthorized request, receive a 401 with a WWW-Authenticate header, and make the request again with the Authorization header (I did not know that, so this was a great learning experience).
The rest template does not send the Authentication header on the initial request (by default it is reactive rather than proactive), so if the service does not respond with a WWW-Authenticate header (as it should according to the HTTP spec) and the RestTemplate does not attempt to send the credentials after the initial response, then the call will simply fail on the intial 401 response.
Fortunately we can tell the rest template to send the credentials on the initial request rather than waiting for a 401 with a WWW-Authenticate header.
Here is the code to do this. The trick here is to override the request factory’s createHttpContext() method to take control over the HTTP context, and use this factory in constructing the RestTemplate. This code works, and uses the self-signed certificate. You may of course restructure it to your taste…
public class BasicRequestFactory extends HttpComponentsClientHttpRequestFactory {
public BasicRequestFactory(HttpClient httpClient) {
super(httpClient);
}
#Override
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
HttpHost targetHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
AuthCache authCache = new BasicAuthCache();
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);
BasicHttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
return localContext;
}
private static HttpClient createSecureClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier());
return HttpClientBuilder.create().setSSLSocketFactory(connectionFactory).build();
}
private static HttpClient createSecureClient(String username, String password) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier());
return HttpClientBuilder.create().setSSLSocketFactory(connectionFactory).setDefaultCredentialsProvider(credentialsProvider).build();
}
public static RestTemplate createTemplate(String username, String password) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
RestTemplate template = new RestTemplate(new BasicRequestFactory(createSecureClient(username, password)));
template.setErrorHandler(new NopResponseErrorHandler());
return template;
}
public static RestTemplate createTemplate() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
RestTemplate template = new RestTemplate(new BasicRequestFactory(createSecureClient()));
template.setErrorHandler(new NopResponseErrorHandler());
return template;
}
private static class NopResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse chr) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse chr) throws IOException {
}
}
}

Related

Spring boot - corrupt authorization header value after RestTemplate.exchange()

I'm having a weird problem, i'm using tokens on Microservice enviroment, I need to call another service with the token already generated.
Call to other service on a Client class... There is the method.
return restTemplate.exchange(URL_REST_METHOD,
HttpMethod.GET,
httpEntity, //HEADER OK
SubscriptionDto.class,
subscriptionId);
To do things easy, I get the previous request, get the token and added to current request.
public class HeaderLoginInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpServletRequest previousRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if(previousRequest.getHeader(AUTHORIZATION) != null){
request.getHeaders().add(AUTHORIZATION, previousRequest.getHeader(AUTHORIZATION));
}
return execution.execute(request, body);
}
}
I attach this interceptor to the Bean RestTemplate
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors
= restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new HeaderLoginInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
The weird thing, after the execution, I see a defect on Authorization header:
Correct one:
Bearer  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOiJBZGlkYXMgQ2hhbGxlbmdlIiwic3...
Weird after:
Bearer  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOiJBZGlkYXMgQ2hhbGxlbmdlIiwic3...
I try adding the token directly before call exchange method, same result.
HttpServletRequest previousRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpHeaders headers = new HttpHeaders();
headers.addAll((MultiValueMap<String, String>) previousRequest.getHeaderNames());
HttpEntity<?> httpEntity = new HttpEntity<>(new HttpHeaders());
In both cases, after call method restTemplate.exchange(...) it corrupts.
Any ideas?
Thanks a lot
After a lot of research, the problem is related to charset of RestTemplate.
If I add this line on #Bean configuration, the problem is solved:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
/*** THE SAVERY LINE ***/
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
/*** ***/
List<ClientHttpRequestInterceptor> interceptors
= restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new HeaderLoginInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
After that, works normally.

Spring Boot RestTemplate: Bad request when directly copying from postman

So I have an API request where I am copying the details directly from postman where it works. I am however getting a bad request error.
#Service
public class GraphApiService {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Autowired
RestTemplate restTemplate;
#Autowired
Constants constants;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public ResponseEntity<String> getAccessTokenUsingRefreshToken(Credential cred) throws IOException{
try {
//https://learn.microsoft.com/en-us/graph/auth-v2-user
// section 5. Use the refresh token to get a new access token
String url = "url";
JSONObject body = new JSONObject();
body.put("grant_type", "refresh_token");
body.put("client_id", "clientid");
body.put("scope","User.Read offline_access Files.Read Mail.Read Sites.Read.All");
body.put("redirect_uri", "http://localhost");
body.put("client_secret","secret");
body.put("refresh_token", "token");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<String>(body.toString(), headers);
ResponseEntity<String> response= restTemplate.postForEntity(url, request,String.class);
return response;
}
catch(HttpClientErrorException e){
logger.error(e.getResponseBodyAsString());
logger.error(e.getMessage());
return null;
}
}
I would appreciate any help. The bad request error message from microsoft graph isn't a descriptive one that will help
You're sending JSON payload with FORM_URLENCODED header.
Either you need to check if API accepts json payload, if so you need to change content-type to application/json or you can post form data as follows.
public ResponseEntity<String> getAccessTokenUsingRefreshToken(Credential cred) throws IOException{
try {
//https://learn.microsoft.com/en-us/graph/auth-v2-user
// section 5. Use the refresh token to get a new access token
String url = "url";
MultiValueMap<String, String> multiValueMap= new LinkedMultiValueMap<String, String>();
multiValueMap.add("grant_type", "refresh_token");
multiValueMap.add("client_id", "clientid");
//.....
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(multiValueMap, headers);
ResponseEntity<String> response= restTemplate.postForEntity(url, request, String.class);
return response;
}catch(HttpClientErrorException e){
logger.error(e.getResponseBodyAsString());
logger.error(e.getMessage());
return null;
}
}

Why did secure Spring WS integration return Unauthorized [401]?

I'm trying to consume a secure third-party SOAP web service in Spring boot, and I'm following Spring WS plus an online resource describing secure integration Spring WS - HTTPS Client-Server Example. After locating the certificate .cer and .jks files under resources folder, and used this code to wrap my configuration:
#Configuration
public class WSGeneralConfig {
#Autowired
#Qualifier("WifiTouristConfig")
CredentailsConfig wifiTouristConfig;
#Autowired
#Qualifier("TradeinWSConfig")
CredentailsConfig tradeinWSConfig;
#Autowired
ClientInterceptor[] interceptors;
#Bean
public WifiToursitWrapper wifiToursitWrapper(#Value("${tibco.wifi.tourist.url}") String uri) throws Exception {
WifiToursitWrapper wifiToursitWrapper = new WifiToursitWrapper();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.usp.tibco.wifi");
wifiToursitWrapper.setMarshaller(marshaller);
wifiToursitWrapper.setUnmarshaller(marshaller);
wifiToursitWrapper.setDefaultUri(uri);
wifiToursitWrapper.setInterceptors(interceptors);
wifiToursitWrapper.setMessageSender(
httpComponentsMessageSender(wifiTouristConfig.getUsername(), wifiTouristConfig.getPassword()));
return wifiToursitWrapper;
}
public HttpComponentsMessageSender httpComponentsMessageSender(String userName, String password) throws Exception {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setCredentials(new UsernamePasswordCredentials(userName, password));
httpComponentsMessageSender.setHttpClient(httpClient());
httpComponentsMessageSender.setAuthScope(AuthScope.ANY);
httpComponentsMessageSender.afterPropertiesSet();
return httpComponentsMessageSender;
}
public HttpClient httpClient() throws Exception {
return HttpClientBuilder
.create()
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext(), NoopHostnameVerifier.INSTANCE))
.addInterceptorFirst(new RemoveSoapHeadersInterceptor()).build();
}
public SSLContext sslContext() throws Exception {
File file = new ClassPathResource("cert/cacerts.jks").getFile();
return SSLContextBuilder.create().loadTrustMaterial(file, "jksPassword".toCharArray()).build();
}
}
The client class also like this
public class WifiToursitWrapper extends WebServiceGatewaySupport {
#SoapOperationName(systemName = SystemName.TIBCO, operationName = "RegisterByIntlNo")
public RegisterByIntlNoResponse registerByIntlNo(RegisterByIntlNoRequest request) {
return (RegisterByIntlNoResponse) getWebServiceTemplate().marshalSendAndReceive(request,
message -> ((SoapMessage) message)
.setSoapAction("/RegisterByIntlNo"));
}
}
Unfortunately I got Unauthorized exception, and I don't know what the root cause of that? here is a snapshot from Exception stack trace
org.springframework.ws.client.WebServiceTransportException: Unauthorized [401]
at org.springframework.ws.client.core.WebServiceTemplate.handleError(WebServiceTemplate.java:699)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:609)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:390)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:383)
at com.etisalat.account.wrapper.WifiToursitWrapper.registerByIntlNo(WifiToursitWrapper.java:25)
I solved the issue by adding Base Authentication to my request, with a good help from this question's answer Consuming SOAP WS returns Error 401 unauthorized , I changed some little code for my case.
private HttpComponentsMessageSender httpComponentsMessageSender(String userName, String password) throws Exception {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setHttpClient(httpClient(userName, password));
return httpComponentsMessageSender;
}
private HttpClient httpClient(String username, String password) throws Exception {
List<Header> headers = new ArrayList<>();
BasicHeader authHeader = new BasicHeader("Authorization", "Basic " + base64authUserPassword(username, password));
headers.add(authHeader);
RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers);
return HttpClientBuilder.create()
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext(), NoopHostnameVerifier.INSTANCE))
.addInterceptorFirst(new RemoveSoapHeadersInterceptor()).addInterceptorLast(reqHeader).build();
}
private String base64authUserPassword(String username, String password) {
String userpassword = username + ":" + password;
String encodedAuthorization = new String(Base64.getEncoder().encode(userpassword.getBytes()));
return encodedAuthorization;
}

Access Http headers in Spring WebSocket Service

I am trying to access the HTTP headers (from the original https upgrade request) in a spring websocket service.
As I understand it I should be able to do this with a HandshakeInterceptor. I have an interceptor like:
public class WebsocketHandshakeInterceptor implements HandshakeInterceptor {
public WebsocketHandshakeInterceptor() {
}
#Override
public boolean beforeHandshake(
final ServerHttpRequest request,
final ServerHttpResponse response,
final WebSocketHandler wsHandler,
final Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
}
return true;
}
#Override
public void afterHandshake(
final ServerHttpRequest request,
final ServerHttpResponse response,
final WebSocketHandler wsHandler,
final Exception exception) {
}
}
But both the request and servletRequest objects have null headers. Note that I am not after a session cookie, as most of the example, but other headers.
I have a test client which makes a request like this:
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
StompSessionHandler sessionHandler = new TestClientStompSessionHandler(getObjectMapper());
final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
if (StringUtils.isNotBlank(token)) {
LOGGER.error("Adding header Authorization: Bearer " + token);
headers.add("Authorization", "Bearer " + token);
}
headers.add("X-Foo", "bar");
stompClient.connect(url, headers, sessionHandler);
Pretty sure it is adding the headers because if I run via my API Gateway which requires the bearer token then it works (with a valid token)
Sorry, my bad. The headers are populated, just the debugger in Eclipse wasn't showing them. Or I was blind. Or both.
HttpHeaders headers = request.getHeaders();
gives me what I need.

Spring RestTemplate.postForEntry, returns 403 error

I want to communicate with a server in Java area by utilizing RestTemplate class.
But when I access to my server with RestTemplate, it shows just 403(forbidden) error.
my controller code :
#Controller(value="homeController")
public class HomeController {
#RequestMapping(value="/test")
public #ResponseBody Map<String, String> test(#RequestParam(value="message", required=false, defaultValue="hell world") String message){
Map<String, String> map = new HashMap<String,String>();
map.put("greeting", message);
return map;
}
client side's code:
#Test
public void test2(){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("message", "test");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class );
System.out.println(response.getBody());
}
If the code works successfully, console should output "test" word.
EDIT:
When I access to the controller, with my web browser, it shows json data correctly.
Now,
How do I fix the code to communicate to server on POST method ?
thanks
As you said you're using spring-security, you can simply add a request Interceptor and add authentication. Spring will create a random password if you don't set one; it will be logged in the console so you can simply copy it from there.
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new BasicAuthorizationInterceptor("username", "yourpassword")));
Another (better) option is to configure the authentication yourself. A simple in memory auth would make
#Configuration
#EnableWebSecurity
public class RestSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.httpBasic();
http.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("username").password("yourpassword").roles("ADMIN");
}
}
You should specify what kinds of HTTP methods your "test" method handles.
You can do this in #RequestMapping annotation this way:
#RequestMapping(path="/test", method = {RequestMethod.GET, RequestMethod.POST},
consumes = "application/x-www-form-urlencoded")

Resources