Spring Framework WebClient not sending request when using Apache HttpComponents - spring

I'm building an application that need to call an endpoint using NTLM authentication. My approach is that I try to use the Apache HttpComponents for the NTLM authentication and integrate the Spring WebClient with it. However, the WebClient doesn't seem to send any request at all. There's no errors but the response won't be returned.
Below is my code:
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(null, -1), new NTCredentials(username, password, computername, domain));
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(RequestConfig.DEFAULT);
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
WebClient.builder().clientConnector(connector).build();
ResponseDto response = webClient.post()
.uri("http://myhost:8080/api/notification/add")
.body(Mono.just(request), RequestDto.class)
.retrieve()
.bodyToMono(ResponseDto.class).block();

Related

Spring Boot Proxy via HttpClient doesn't work

I'm trying to setup a WebClient connection in Spring Boot using a proxy. My implementation looks like the following:
final WebClient.Builder webclientBuilder = WebClient.builder();
final HttpClient httpClient = HttpClient.create();
httpClient.proxy(proxy -> proxy
.type(Proxy.HTTP)
.host(proxyName)
.port(Integer.parseInt(proxyPort)));
final ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
webclientBuilder.clientConnector(connector);
final WebClient webClient = webclientBuilder
.baseUrl(baseUrl)
.build();
After running it and sending an API call, I receive a "Connection timed out: no further information". I should get back a Bad Request (in case my call is wrong), but I don't.
Is the implementation wrong?
the proxyName is written like this: "proxy.blabla.de"
After some trial and error and comparing I found a solution working for me:
String baseUrl = "https://mybaseurl";
String proxyName = "proxy.blabla.de";
int proxyPort = 1234;
public InitResponse addAccount() {
// for logging purposes, nothing to do with the proxy
LOGGER.info("LOGGER.info: addAccount()");
final InitRequest request = buildRequest();
HttpClient httpClient = HttpClient.create()
.proxy(proxy -> proxy.type(Proxy.HTTP)
.host(proxyName)
.port(proxyPort));
ReactorClientHttpConnector conn = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().clientConnector(conn).baseUrl(baseUrl).build();

Problem with NTLM authentication on executing http request by Spring WebClient and HttpComponentsClientHttpConnector

I try to execute http request using Apache HttpClient 5 and I need NTLM authentication, which is supported by this client out of the box. But when I try to use it with Spring WebClient by HttpComponentsClientHttpConnector, there is an issue.
HttpComponentsClientHttpConnector initializes HttpComponentsClientHttpRequest to execute request. But if request has body, HttpComponentsClientHttpRequest initializes ReactiveEntityProducer, which is not repeatable, so that, while I need to execute 3 requests to authenticatу using NTLM, only one actually happens.
Is it possible to change this behavior?
UPD
Added my prototype java-code:
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope("https", "some-host", 443, null, StandardAuthScheme.NTLM),
new NTCredentials("test-user", "test-password".toCharArray(), null, null)
);
WebClient webClient = WebClient.builder()
.clientConnector(new HttpComponentsClientHttpConnector
(HttpAsyncClients
.custom()
.setDefaultCredentialsProvider(credsProvider)
.setTargetAuthenticationStrategy(DefaultAuthenticationStrategy.INSTANCE)
.setDefaultRequestConfig(
RequestConfig.custom()
.setAuthenticationEnabled(true)
.setTargetPreferredAuthSchemes(Collections.singletonList(StandardAuthScheme.NTLM))
.setExpectContinueEnabled(true)
.build())
.build()))
.build();
webClient
.method(HttpMethod.POST)
.uri("https://some-host/execute")
.header("Content-Type", "text/xml; charset=utf-8")
.header("SOAPAction", "http://schemas.microsoft.com/exchange/services/2006/messagesCreateItem")
.header("Accept-Encoding", "gzip, deflate, br")
.header("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
.header("Expect", "100-continue")
.body(BodyInserters.fromValue(
"<SOME_XML_BODY>"
))
.exchangeToMono(clientResponse -> {
return clientResponse.bodyToMono(Object.class);
})
.subscribe();

How to authorize against an OpenId Connect secured REST-API programmatically?

I have implement a REST-API based on Spring Boot secured by Spring Security 5.2 OpenID Connect resource server. The authorization server is an IdentityServer4. So far so good, the authentication using Bearer Token (the token is determined via a dummy web page) works well.
The challenge now is to call the REST API from a client that does not require user interaction (web page).
I would like to provide the API users with an unsecured endpoint (/authorization) which can be used to receive the Bearer Token for any further secured service. Username and password should be passed as request parameters.
I have search the web and studied the docs from Spring but I did not have found something which addresses my use case.
I implemented a relatively simple solution
#GetMapping
public ResponseEntity<GetTokenResponse> getToken(#RequestBody GetTokenRequest getTokenRequest) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("client_id", clientId);
formData.add("client_secret", clientSecret);
formData.add("grant_type", "password");
formData.add("scope", scopes);
formData.add("username", getTokenRequest.getUsername());
formData.add("password", getTokenRequest.getPassword());
RestTemplate restTemplate = new RestTemplate();
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(formData, headers);
ResponseEntity<GetTokenResponse> response = restTemplate.postForEntity( tokenEndPoint, request , GetTokenResponse.class );
String accessToken = response.getBody().getAccessToken();
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
Jwt jwt = decoder.decode(accessToken);
logger.debug("Headers:\n{}", jwt.getHeaders());
logger.debug("Claims:\n{}", jwt.getClaims());
logger.info("User {}, {} '{}' authorised.", jwt.getClaimAsString("given_name"), jwt.getClaimAsString("family_name"), jwt.getClaimAsString("sub"));
return response;
}
The response contains the bearer token and can therefore be used for the API calls.

Spring Security 5.2 Password Flow

I am trying to authenticate the user using the password flow in the latest version of Spring Security - 5.2.
The docs seem to suggest how to do that.
#Bean
public OAuth2AuthorizedClientManager passwordFlowAuthorizedClientManager(
HttpClient httpClient,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
DefaultPasswordTokenResponseClient c = new DefaultPasswordTokenResponseClient();
RestTemplate client = new RestTemplate(requestFactory);
client.setMessageConverters(Arrays.asList(
new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()));
client.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
c.setRestOperations(client);
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password(configurer -> configurer.accessTokenResponseClient(c))
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
authorizedClientManager.setContextAttributesMapper(authorizeRequest -> {
Map<String, Object> contextAttributes = new HashMap<>();
String username = authorizeRequest.getAttribute(OAuth2ParameterNames.USERNAME);
String password = authorizeRequest.getAttribute(OAuth2ParameterNames.PASSWORD);
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
return contextAttributes;
});
return authorizedClientManager;
}
I execute the request, I can see the access token returned in HTTP header but the SecurityContext is not populated and the session user remains anonymous.
String username = "joe";
String password = "joe";
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
ClientRegistration r = clientRegistrationRepository.findByRegistrationId("keycloak");
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(r.getRegistrationId())
.principal(authentication)
.attributes(attrs -> {
attrs.put(OAuth2ParameterNames.USERNAME, username);
attrs.put(OAuth2ParameterNames.PASSWORD, password);
})
.build();
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
Any ideas?
After reading into the documentation a bit more I do not think that Oauth 2 password flow in Spring Security 5.2 is supported the same way authorisation flow is. Spring Security 5.2 has password flow support for the http client which can cache the authorization request and refresh the token before it expires - but there is no end user password flow support in which the client proxies the credentials to the authorization server.
Of course, it is entirely possible to authenticate the end user by harvesting the credentials, implementing a custom AuthenticationProvider that swaps the credentials for a token with the authorization server and returns an OAuth2AuthenticationToken that is persisted to the context.

Spring REST template - 401 Unauthorized error

I am using Spring Rest Template inside a Spring Boot Application.
I always get 401 Unauthorized error even though I am passing the credentials.
I am able to access this service by Chrome REST Web Service Client.
Is there a simplified way to access the REST template in SpringBoot.
Below is the code snippet done so far which results in 401 error
private DetailsBean invokeDetailsRestService(UserParam userParam){
ResponseEntity<DetailsBean> responseEntity = null;
String url = "https://dev.com/app/identifyuser/";
RestClientConfig restClientConfig =new RestClientConfig("user123","pass123");
responseEntity= restClientConfig.postForEntity(url, userParam, DetailsBean.class);
log.debug("User Details : {} ", responseEntity.getBody());
return responseEntity.getBody();
}
public ClientHttpRequestFactory getRequestFactory(String userName,String password){
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials( new AuthScope(null, -1), new UsernamePasswordCredentials(userName,password) );
HttpClient httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
RestClientConfig class
public RestClientConfig(String username, String password) {
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(null, -1),
new UsernamePasswordCredentials(username, password));
HttpClient httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
}
Error:
WARN c7af55b5-1cac-4db6-a202-202416c27ba4
12612 --- [apr-8082-exec-8] o.a.http.impl.auth.HttpAuthenticator
: NEGOTIATE authentication error:
No valid credentials provided (Mechanism level:
No valid credentials provided (Mechanism level:
Failed to find any Kerberos tgt))
The authorization issue was fixed with the below code..
Credentials should be passed to a Spring REST Template with the below code:
String userAndPass = "Test:Test123";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
headers.add("Authorization", "Basic " + Base64Utility.encode(userAndPass.getBytes()));
I faced similar issue when i'm trying to make call to webservice, this solved my issue:
restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor("userName", "password"));
restTemplate.postForObject('','',''');
Pass the credentials like this, it should solve the issue.
I used spring boot 2.2.4.RELEASE version. then I work below way.
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(username, password));
RequestDto requestDto = new RequestDto();
// set parameter
ResponseDto response = restTemplate.postForObject(URL, requestDto, ResponseDto.class);
Or
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, password);
RequestDto requestDto = new RequestDto();
// set parameter
HttpEntity<RequestDto> request = new HttpEntity<>(requestDto, headers);
ResponseDto response = restTemplate.postForObject(URL, request, ResponseDto.class);

Resources