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.
Related
I'm writing a Flutter web project with a Spring boot backend and am really battling with getting the authentication stuff to work.
In flutter web I have a "sign_in" method which receives an email and password and passes it to a repository method which sends a post request to the server. See code below. Currently it appears as if the post never returns as the "done with post" line never prints.
Future<String> signIn(String email, String password) async {
authenticationRepository.setStatus(AuthenticationStatus.unknown());
print('signIn user: email: $email pw: $password');
User user = User('null', email, password: password);
//print('user: $user');
var url;
if (ServerRepository.SERVER_USE_HTTPS) {
url = new Uri.https(ServerRepository.SERVER_ADDRESS,
ServerRepository.SERVER_AUTH_LOGIN_ENDPOINT);
} else {
url = new Uri.http(ServerRepository.SERVER_ADDRESS,
ServerRepository.SERVER_AUTH_LOGIN_ENDPOINT);
}
// print('url: $url');
var json = user.toUserRegisterEntity().toJson();
print('Sending request: $json');
// var response = await http.post(url, body: json);
var response = await ServerRepository.performPostRequest(url, jsonBody: json, printOutput: true, omitHeaders: true );
print('Response status: ${response.statusCode}');
print('Response body b4 decoding: ${response.body}');
Map<String, dynamic> responseBody = jsonDecode(response.body);
print('Response body parsed: $responseBody');
if (response.statusCode != 201) {
authenticationRepository
.setStatus(AuthenticationStatus.unauthenticated());
throw FailedRequestError('${responseBody['message']}');
}
User user2 = User(
responseBody['data']['_id'], responseBody['data']['email'],
accessToken: responseBody['accessToken'],
refreshToken: responseBody['refreshToken']);
print('user2 $user2');
authenticationRepository
.setStatus(AuthenticationStatus.authenticated(user2));
return responseBody['data']['_id']; // return the id of the response
}
static Future<Response> performPostRequest(Uri url, {String? accessToken, var jsonBody, bool printOutput = false, bool omitHeaders=false} ) async {
var body = json.encode(jsonBody ?? '');
if(printOutput){
print('Posting to url: $url');
print('Request Body: $body');
}
Map<String, String> userHeader = {
HttpHeaders.authorizationHeader: 'Bearer ${accessToken ?? 'accessToken'}',
"Content-type": "application/json",
};
if(omitHeaders){
userHeader = { };
}
print('performing post: ');
var response = await http.post(
url,
body: body,
headers: userHeader,
);
print('done with post?!');
if(printOutput){
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
Map<String, dynamic> responseBody = jsonDecode(response.body);
print('Response body parsed: $responseBody');
}
return response;
}
My console output is as follows when attempting the request:
signIn user: email: XXXXXX#gmail.com pw: XXxxXXx500!
Sending request: {email: XXXXXX#gmail.com, password: XXxxXXx500!}
Posting to url: http://localhost:8080/auth/login
Request Body: {"email":"XXXXXX#gmail.com","password":"XXxxXXx500!"}
performing post:
So it seems like the response is never sent by the server.
On my server, using Spring boot security the setup is as follows (I based it from this tutorial). Securityconfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final JWTUtils jwtTokenUtil;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(jwtTokenUtil, authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/auth/login");
http.csrf().disable();
//http.cors(); //tried but still no repsonse
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers( "/auth/**").permitAll(); // no restrictions on this end point
http.authorizeRequests().antMatchers(POST, "/users").permitAll();
http.authorizeRequests().antMatchers(GET, "/users/**").hasAnyAuthority("ROLE_USER");
http.authorizeRequests().antMatchers(POST, "/users/role/**").hasAnyAuthority("ROLE_ADMIN");
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
And the filter handling the "/auth/login" end point:
#Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JWTUtils jwtTokenUtil;
private final AuthenticationManager authenticationManager;
#Autowired
public CustomAuthenticationFilter(JWTUtils jwtTokenUtil, AuthenticationManager authenticationManager) {
this.jwtTokenUtil = jwtTokenUtil;
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("attemptAuthentication");
log.info("type "+request.getHeader("Content-Type"));
try {
//Wrap the request
MutableHttpServletRequest wrapper = new MutableHttpServletRequest(request);
//Get the input stream from the wrapper and convert it into byte array
byte[] body;
body = StreamUtils.copyToByteArray(wrapper.getInputStream());
Map<String, String> jsonRequest = new ObjectMapper().readValue(body, Map.class);
log.info("jsonRequest "+jsonRequest);
String email = jsonRequest.get("email");
String password = jsonRequest.get("password");
log.info("jsonRequest username is "+email);
log.info("jsonRequest password is "+password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
} catch (IOException e) {
e.printStackTrace();
}
//if data is not passed as json, but rather form Data - then this should allow it to work as well
String email = request.getParameter("email");
String password = request.getParameter("password");
log.info("old username is "+email);
log.info("old password is "+password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("successfulAuthentication");
User user = (User) authResult.getPrincipal();
String[] tokens = jwtTokenUtil.generateJWTTokens(user.getUsername()
,user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())
, request.getRequestURL().toString() );
String access_token = tokens[0];
String refresh_token = tokens[1];
log.info("tokens generated");
Map<String, String> tokensMap = new HashMap<>();
tokensMap.put("access_token", access_token);
tokensMap.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
log.info("writing result");
response.setStatus(HttpServletResponse.SC_OK);
new ObjectMapper().writeValue(response.getWriter(), tokensMap);
}
}
When I try the "auth/login" endpoint using postman, I get the correct response with the jwt tokens. See below:
I'm really stuck and have no idea how to fix it. I've tried setting cors on, changing the content-type (which helped making the server see the POST request instead of an OPTIONS request). Any help/explanation would be greatly appreciated.
After lots of trial and error I stumbled across this answer on a JavaScript/ajax question.
It boils down to edge/chrome not liking the use of localhost in a domain. so, if you're using a Spring Boot server, add the following bean to your application class (remember to update the port number):
#Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:56222"));
corsConfiguration.setAllowedHeaders(Arrays.asList("Origin","Access-Control-Allow-Origin",
"Content-Type","Accept","Authorization","Origin,Accept","X-Requested-With",
"Access-Control-Request-Method","Access-Control-Request-Headers"));
corsConfiguration.setExposedHeaders(Arrays.asList("Origin","Content-Type","Accept","Authorization",
"Access-Control-Allow-Origin","Access-Control-Allow-Origin","Access-Control-Allow-Credentials"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET","PUT","POST","DELETE","OPTIONS"));
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
In my current project, I am using OAuth2 for token-based authentication in order to access the Rest APIs, but these tokens are readable by js. Because of this and a couple of other reasons I wanted to store the access token in cookies.
I have gone through the internet and could not find a way to put tokens in cookies. Can someone please help me with this?
Finally, found a solution for this. I have created a /login API where I am setting access token in cookies.
#PostMapping(consumes = "application/json")
public ResponseEntity<?> login(#RequestBody LoginRequest loginRequest,
HttpServletResponse httpResponse) throws Exception {
ResponseEntity<?> result = null;
try {
String url = UriComponentsBuilder.fromHttpUrl(environment.getProperty("oauth.token.url"))
.queryParam("username", loginRequest.getUsername())
.queryParam("password", loginRequest.getPassword())
.queryParam("grant_type", OauthConstants.GRANT_TYPE_PASSWORD)
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add(AppConstants.AUTHORIZATION_HEADER, AppConstants.AUTH_HEADER_CLIENT_DEFAULT);
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
ResponseEntity<HashMap> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, HashMap.class);
Map<String, Object> authMap = response.getBody();
logger.info("Adding cookies");
String accessToken = (String) authMap.get(AppConstants.ACCESS_TOKEN);
String refreshToken = (String)authMap.get(AppConstants.REFRESH_TOKEN);
List<Cookie> cookies = new ArrayList<>();
cookies.add(newAppCookie(AppConstants.ACCESS_TOKEN, accessToken));
cookies.add(newAppCookie(AppConstants.REFRESH_TOKEN, refreshToken));
cookies.stream().forEach(c -> httpResponse.addCookie(c));
logger.info("Cookies added successfully");
result = ResponseEntity.ok(authMap);
} catch (HttpClientErrorException hex) {
logger.error("HttpClientErrorException occurred in login(): ", hex);
result = new ResponseEntity<>(hex.getResponseBodyAsString(),
HttpStatus.UNAUTHORIZED);
} catch (Exception e) {
logger.error("Exception occurred in login(): ", e);
throw e;
}
return result;
And after user logs in, for every API request to server a Filter is applied to check the access token in the cookies is valid or not as shown below.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationOAuthFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
#Autowired
private Environment environment;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (httpRequest.getRequestURI().equals("/oauth/token")||
httpRequest.getRequestURI().equals("/login")) {
chain.doFilter(request, response);
return;
}
Cookie[] cookies = httpRequest.getCookies();
if (cookies == null) {
logger.info("No Cookies found");
chain.doFilter(request, response);
return;
}
Map<String,String> cookiesMap = Arrays.asList(cookies).stream().collect(Collectors.toMap(Cookie::getName, Cookie::getValue));
if (!cookiesMap.containsKey(AppConstants.ACCESS_TOKEN)) {
logger.info("No Access token found in cookie");
chain.doFilter(request, response);
return;
}
ApplicationRequestWrapper mutableRequest = new ApplicationRequestWrapper(httpRequest);
mutableRequest.putHeader("Authorization","Bearer "+ cookiesMap.get(AppConstants.ACCESS_TOKEN));
logger.info("Access token found in cookie");
chain.doFilter(mutableRequest, response);
}
I am following the link https://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v & the github project https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2. I am able to login in to the OAuth provider and get the authorization code back in the client.
Now I make the following call from the client to get the token from the provider (provider is on port 9999)
HttpHeaders headers = new HttpHeaders();
headers.add("Accept",MediaType.APPLICATION_JSON_VALUE);
List<String> cookies = httpEntity.getHeaders().get("Cookie");
headers.put("Cookie", cookies);
String redirectURL= "http://localhost:9999/oauthprovider/oauth/token" + "?" + "response_type=token" + "&" + "grant_type=authorization_code" + "&" + "client_id=acme"+ "&" + "client_secret=acmesecret"+ "&" + "redirect_uri=http://localhost:8081/callback"+"&" + "code=" + authCode + "&" + "state=" + stateValue;
HttpEntity<String> redirectResponse = template.exchange(
redirectURL,
HttpMethod.POST,
responseentity,
String.class);
result=redirectResponse.toString()
The result variable value has the following.(I have disabled csrf and sending client_secret as a query parameter (for the time being), although they are not recommended)
<302 Found,{X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Location=[http://localhost:9999/oauthprovider/oauthlogin], Content-Length=[0], Date=[Thu, 09 Nov 2017 12:07:37 GMT]}>
In the console I have these
Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 2B669DF59BCE8047849BFBCA148BEE67; Granted Authorities: ROLE_ANONYMOUS
Does I am redirecting back to login(I am getting it in the logs as mentioned before), since the role is ROLE_ANONYMOUS? How can I fix the issue?
Adding more details on the code (Did only minor changes from the sample code provided in the link). Providers's context path is /oauthprovider and with curl call I am getting the token.
#Configuration
#EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
//................................
//................................
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code", "refresh_token",
"password").scopes("openid").autoApprove(true).redirectUris("http://localhost:8081/callback");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager).accessTokenConverter(
jwtAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
}
#Configuration
#Order(-20)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.formLogin().loginPage("/oauthlogin").loginProcessingUrl("/login").failureUrl("/login?error=true").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauthlogin", "/oauth/authorize", "/oauth/token" ,"/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
http.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
}
}
For calling token endpoint you need to send Authorization header with value base64(client_id:client_secret) and in body you should send username , password ,grant_type as FORM_URLENCODED:
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);
// params
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_TOKEN_URL.replace(OAUTH_HOST, oauthHost), request,
OauthTokenRespone.class);
} catch (Exception ex) {
ex.printStackTrace();
}
}
#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
}
Something went wrong in my last day's testing. I can get access token if my handler is either in client or in provider. If my redirect handler is in provider the user is not anonymous as per the debug logs (may be due to session..?)But looks like I have to use one redirect_url consistently.(Otherwise I get redirect_uri mismatch error..)
Following is the working code that gets json response..
#ResponseBody
#RequestMapping(value = "/clientcallback", method = RequestMethod.GET)
public ResponseEntity<OauthTokenResponse> redirectCallback(#RequestParam (value= "code", defaultValue="") String authCode,#RequestParam (value= "state", defaultValue="") String stateValue,HttpEntity<String> httpEntity)
{
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
String plainCreds = "acme:acmesecret";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
List<String> cookies = httpEntity.getHeaders().get("Cookie");
if(cookies != null)
{
headers.put("Cookie", cookies);
}
else
{
cookies = httpEntity.getHeaders().get("Set-Cookie");
headers.put("Set-Cookie", cookies);
}
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
headers);
OauthTokenResponse res = null;
try {
res = restTemplate.postForObject("http://localhost:9999/uaa/oauth/token?grant_type=authorization_code&client_id=acme&redirect_uri=http://localhost:8081/clientcallback&code=" + authCode, request,
OauthTokenResponse.class);
} catch (Exception ex) {
ex.printStackTrace();
}
return new ResponseEntity<OauthTokenResponse>(res, HttpStatus.OK);
}
Thanks again for the tips..
I know this is old but I've recently faced a very similar issue so I'm posting my solution.
I used Simle SSO Example as a base for my modifications. I'm using spring security filter which is mapped to / (web root) and spring oauth with endpoints mapped to /auth/*. When I try to access /auth/oauth/token I get redirect to login page. After some debugging I found out the cause:
By using #EnableAuthorizationServer you are importing AuthorizationServerSecurityConfiguration which secures endpoints /oauth/token, /oauth/token_key and /oauth/check_token. Everything is going to work with this default configuration as long as your authorization server is mapped to the web root. In my case, requests to /auth/oauth/token were simply redirected to login page because spring security could not find a rule for this path.
My solution was to manualy secure those endpoints with /auth prefix in my spring security configuration.
Hope this helps.
I am writing a rest proxy (it exposes the API and delegates call to other server) and it works fine for the normal case and also for 500 http status code, we get the response from the rest client.
But when we get 404 status code, the Rest API server returns the message but we get junk values from the RestTemplate. We need to pass the same response to other API user but cannot get the same response.
Message returned from REST API Server:
{
"status_code":"0",
"error":{
"code":"404",
"description":"Source not found"
}
}
Getting the below response by RestTemplate client:
Not able to paste the content, attaching the screen shot of the response.
Please see the code below.
#RequestMapping(value = "/api/**")
public #ResponseBody String apiProxy(#RequestBody String body, HttpMethod method, HttpServletRequest request,
HttpServletResponse response) throws URISyntaxException {
RestTemplate restTemplate = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
restTemplate.setInterceptors(Collections.singletonList(new RestClientLoggingInterceptor()));
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
HttpHeaders httpHeaders = new HttpHeaders();
Enumeration<String> headers = request.getHeaderNames();
String headerName = null;
String headerValue = null;
while (headers.hasMoreElements()) {
headerName = headers.nextElement();
headerValue = request.getHeader(headerName);
httpHeaders.set(headerName, headerValue);
}
HttpEntity<String> httpEntity = new HttpEntity<String>(body, httpHeaders);
URI uri = new URI(ServerProtocol, null, ServerDomain, Integer.valueOf(ServerPort),
request.getRequestURI(), request.getQueryString(), null);
ResponseEntity<String> responseEntity = null;
try {
responseEntity = restTemplate.exchange(uri, method, httpEntity, String.class);
} catch (RestClientResponseException e) {
response.setStatus(e.getRawStatusCode());
return e.getResponseBodyAsString();
}
response.setStatus(responseEntity.getStatusCode().value());
return responseEntity.getBody();
}
ResponseErrorHandler Class
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
private static final Logger logger = LogManager.getLogger(CustomResponseErrorHandler.class);
#Override
public void handleError(ClientHttpResponse response) throws IOException {
logger.error("Response error: {} {}", response.getStatusCode(), response.getStatusText());
}
}
RestClientLoggingInterceptor Class
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
ClientHttpResponse response = execution.execute(request, body);
logger.debug("request method:" + request.getMethod());
logger.debug("request URI:" + request.getURI());
logger.debug("request headers:" + request.getHeaders());
logger.debug("request body:" + new String(body, Charset.forName("UTF-8")));
logger.debug("response status code:" + response.getStatusCode());
logger.debug("response headers:" + response.getHeaders());
logger.debug("response body:" + IOUtils.toString(response.getBody(), "UTF-8"));
return response;
}
Thanks
Cannot parse gzip encoded response with RestTemplate from Spring-Web
This was helpful to me for this same issue. You can try this out.
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 {
}
}
}