NTLM authentication in Apache HttpClient - apache-commons-httpclient

I am building an application that reads JSON response from certain endpoints and I am trying to authenticate in Apache HttpClient using NTLM authentication:
The class that is responsible for authentication HttpConnector tries to authentice right after its instantiation:
public static HttpConnector on(String username, String password) {
HttpConnector connector = new HttpConnector(username, password);
Credentials credentials = new NTCredentials(username, password, "host", "domain");
connector.getHttpClient().getState().setCredentials(AuthScope.ANY, credentials);
return connector;
}
but I always get response code 401 Unauthorized. As I read in internet including here in Stackoverflow I used NTCredentials that I am trying to set globally in the HttpClient. I have tested the endpoints in Postman, there I get the JSON response successfully but HttpClient cannot connect.
In the code I use GetMethod: httpMethod = new GetMethod(url);
Here I have also tried to configure authentication but it still does not work:
private void configureMethod(HttpMethod httpMethod) throws MalformedChallengeException {
httpMethod.getHostAuthState().setAuthRequested(true);
httpMethod.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
httpMethod.setDoAuthentication(true);
httpMethod.getParams().setParameter(
HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
httpMethod.getHostAuthState().setAuthRequested(true);
httpMethod.getHostAuthState().setAuthScheme(
new NTLMScheme(
String.format("ntlm %s:%s", username, password)));
}
During debugging I see that I get: Connection reset by peer: socket write error. It happens in HttpMethodDirector::executeWithRetry(final HttpMethod method) method.
Can someone help me, what is the correctNTLM authentication setup in Apache HttpClient. Can I really use the global set of credentials or I have to setup credentials to every HttpMethod I create and how?
Thank you in advance!

I fixed this by formatting the client the following way:
HttpClient httpClient = new DefaultHttpClient();
httpClient.getCredentialsProvider().setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
new NTCredentials(username, password, HOST, MY_DOMAIN));
And the used not GetMethod but HttpGet:
HttpGet = getRequest = new HttpGet(url);
and this time the connection owas successful.

Related

Spring Framework WebClient not sending request when using Apache HttpComponents

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();

OAuth2.0 authorization spring boot web application flow

i am using java library client for web application authentication, i produce authorization url using client secret and client id,also i provided a redirect url within google api console,but i don't know if it is necessary for me to create this server to receive refresh token?
i mean in production i should provide a separate server to receive the refresh token?(redirect url comes to this server)
the main problem is user should paste the produced url on browser by himself but i want to open browser authmaticly , the second one is about reciving the refresh token i am not sure about creating another server to recieve refreshcode and i can't use service accounts i am going with web flow authentication.
UserAuthorizer userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create(OAUTH2_CALLBACK_URL_CONFIGURED_AT_GOOGLE_CONSOLE))
.build();
baseUri = URI.create("http://localhost:" + simpleCallbackServer.getLocalPort());
System.out.printf(
"Paste this url in your browser:%n%s%n",
userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri));
and this is local server to receive refresh token:
private static class SimpleCallbackServer extends ServerSocket {
private AuthorizationResponse authorizationResponse;
SimpleCallbackServer() throws IOException {
// Passes a port # of zero so that a port will be automatically allocated.
super(0);
}
/**
* Blocks until a connection is made to this server. After this method completes, the
* authorizationResponse of this server will be set, provided the request line is in the
* expected format.
*/
#Override
public Socket accept() throws IOException {
Socket socket = super.accept();
}
}
for those who struggling to get authorized using google oauth2.0 with spring boot
you cant redirect user to authorization url(which google authorization server gives to using your client id and client secret) use a controller to redirect user:
#GetMapping(value = "/redirect-user")
public ResponseEntity<Object> redirectToExternalUrl() throws URISyntaxException {
String url=gs.createUserAuthorizationUrl();
URI authorizationUrl = new URI(url);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(authorizationUrl);
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
at service layer createUserAuthorizationUrl() method is like below:
public String createUserAuthorizationUrl() {
clientId = "client-id";
clientSecret = "client-secret-code";
userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create("/oauth2callback"))
.build();
baseUri = URI.create("your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port"); //giving redirect url
String redirectURL = userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri).toString();
return redirectURL;
}
and let's create a controller to support the get request comming from google authorization server with an code. we are going to use that code to get access token from google.i get state and code by #RequestParam
and i also want to redirect user to my application.
#GetMapping(value = "/oauth2callback")
public ResponseEntity<Object> proceedeTOServer(#RequestParam String state,
#RequestParam String code) throws URISyntaxException {
String url="my-application-url-to-redirect-user";
URI dashboardURL = new URI(url);
HttpHeaders httpHeaders=new HttpHeaders();
httpHeaders.setLocation(dashboardURL);
gs.getCode(state,code);
return new ResponseEntity<>(httpHeaders,HttpStatus.FOUND);
}
and in getCode(code) in service layer i am going to send to code and receive the refresh token or access token:
UserCredentials userCredentials =userAuthorizer.getCredentialsFromCode(code, "your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port");

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.

Share SPRING_SECURITY_CONTEXT between two applications

I have two different Spring Boot Applications that run on localhost on different ports (8080, 8081) and different configs (application.yml). These apps use SSO with OAuth 2.0 to get authorization token from Authorization Server. I log in to my first application, get authorization and everything works great here. Now I need to share these authentication details with second Spring Boot App (on port 8081) to authorize second app in Authorization Server. Googled and found 2 aproaches: I can try to share HttpSession between two apps (but I think it's redundant) OR HttpSessionSecurityContextRepository as SecurityContextRepository which seems more convenient. The problem here is that I can't manage to do so and I'm still not sure that it's a good idea to share Security Context between 2 apps.
What I tried for now:
Share authorization token from first app via headers in GET request (custom-built in accordance with specification for requests for Authorization Server), but it didn't work - second app doesn't take in mind this token.
Share authorized cookie from first app to second, but it didn't work, too.
I can't do authorization through Authorization Server on second app because it may be not a Spring Boot App with #Controller but any other app without HTML forms, so I need to authorize on first app (with UI), get all the data which is needed to perform authorized requests and pass it to second app (third, fourth...) so they will be able to do authorized requests too.
Thanks in advance!
I presume that your authorization/resource server is external application.And you can login successfully with your first application so flow is working.You have two client application with own client_id, client_secret and etc. parameters.If these parameters are different then authorization/resource server will return different bareer token and sessionid cookie for first and second client application.Otherwise you need to authorize both of them in authorization/resource server.
I would offer when user do login to first app then in background you do login also for second application.
For automatically authorizing second application you can try to do oauth2 login flow manually for second application with own parameters when after successful first application login and send cookies to frontend which you got from oauth2 login.
For manual oauth2 login you can try below code:
private Cookie oauth2Login(String username, String password, String clientId, String clientSecret) {
try {
String oauthHost = InetAddress.getByName(OAUTH_HOST).getHostAddress();
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
// Basic Auth
String plainCreds = clientId + ":" + clientSecret;
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
// form param
map.add("username", username);
map.add("password", password);
map.add("grant_type", GRANT_TYPE);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
headers);
// CALLING TOKEN URL
OauthTokenRespone res = null;
try {
res = restTemplate.postForObject(OAUTH_HOST, request,
OauthTokenRespone.class);
} catch (Exception ex) {
ex.printStackTrace();
}
Optional<OauthTokenRespone> optRes = Optional.ofNullable(res);
String accessToken = optRes.orElseGet(() -> new OauthTokenRespone("", "", "", "", "", ""))
.getAccess_token();
// CALLING RESOURCE
headers.clear();
map.clear();
headers.setContentType(MediaType.APPLICATION_JSON);
map.add("access_token", accessToken);
request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
Cookie oauthCookie = null;
if (accessToken.length() > 0) {
HttpEntity<String> response = restTemplate.exchange(
OAUTH_RESOURCE_URL.replace(OAUTH_HOST, oauthHost) + "?access_token=" + accessToken,
HttpMethod.POST, request, String.class);
String cookie = Optional.ofNullable(response.getHeaders().get("Set-Cookie"))
.orElseGet(() -> Arrays.asList(new String(""))).get(0);
if (cookie.length() > 0) {
String[] c = cookie.split(";")[0].split("=");
oauthCookie = new Cookie(c[0], c[1]);
oauthCookie.setHttpOnly(true);
}
}
return Optional.ofNullable(oauthCookie).orElseGet(() -> new Cookie("Ops", ""));
} catch (Throwable t) {
return new Cookie("Ops", "");
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class OauthTokenRespone {
private String access_token;
private String token_type;
private String refresh_token;
private String expires_in;
private String scope;
private String organization;
// getter and setter
}
And call this method after first app login as follows :
Cookie oauthCookie = oauth2Login(authenticationRequest.getUsername(), authenticationRequest.getPassword(),
CLIENT_ID, CLIENT_SECRET);
After getting cookie you need change its name (for example JSESSIONID-SECOND) because same cookies will override each other and also need to change its domain path to second app domain.
response.addCookie(oauthCookie);
Last you need add cookie to response (it is HttpServletResponse reference).
Hope it helps!

How to access Spring REST API in JHipster with Spring RestTemplate

I have set up JHipster like described on its homepage with some entities. Frontend with AngularJS works great and also the API page, lets me test my services as expected.
Now I am trying to write a REST-Client using Spring's RestTemplate like this:
public List<SomeEntity> getAllEntities(){
URI uri = URI.create("http://localhost:8080/api/entities");
HttpHeaders httpHeaders = this.createHeaders("admin", "admin")
ResponseEntity<SomeEntity[]> responseEntity = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<SomeEntity>(httpHeaders), SomeEntity[].class);
return Arrays.asList(responseEntity.getBody());
}
private HttpHeaders createHeaders(final String username, final String password ){
HttpHeaders headers = new HttpHeaders(){
{
String auth = username + ":" + password;
byte[] encodedAuth = Base64.encode(
auth.getBytes(Charset.forName("US-ASCII")) );
String authHeader = "Basic " + new String( encodedAuth );
set( "Authorization", authHeader );
}
};
headers.add("Content-Type", "application/json");
headers.add("Accept", "application/json");
return headers;
}
But this results in the following error:
[WARN] org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/api/entities" resulted in 401 (Unauthorized); invoking error handler
Now I am not sure, if and how I need to adapt my HttpHeaders or if my simple basic-auth handling approach at all is wrong.
The way you authenticate is wrong, it seems you chose session authentication when generating your app, so this requires form-based auth not http basic auth and it requires being able to store session cookie and CSRF cookie so most likely using commons http client.
Maybe choosing xauth token authentication when generating your app would be simpler.
Once you get this working you will have CORS issues as soon as your client won't run on same host as your JHipster app.

Resources