I have oauth token implemented on server side but upon Invalid token or Token expirey i am getting 200 http status code but in response body i have
{"code":"4XX", "data":{"some":"object"}
When i try to read string in interceptor i get okhttp dispatcher java.lang.illegalstateexception closed because response.body().string() must be called only once.
Also i read from here Refreshing OAuth token using Retrofit without modifying all calls that we can use OkHttp Authenticator class but it works only with 401/407 i havent triedn as i will not get this. Is there any way we can customize Authenticator and proceed our logic inside it.
Thank you
If it possible, try to talk with your server side about response codes. Communication is also a very important skill.
If it inpossible, you can modify response codes manually with reflection, it enables okHttp authentication logic.
public OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.authenticator((route, response) -> {
System.out.println("it working");
return null;
})
.addNetworkInterceptor(new UnauthorizedCaseParserInterceptor())
.build();
}
public class UnauthorizedCaseParserInterceptor implements Interceptor {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (isUnauthorizedResponse(response)) {
try {
Field codeField = response.getClass().getDeclaredField("code");
codeField.setAccessible(true);
codeField.set(response, HttpURLConnection.HTTP_UNAUTHORIZED);
} catch (Exception e) {
return response;
}
}
return response;
}
private boolean isUnauthorizedResponse(Response response) {
//parse response...
}
}
Please use this solution only as a last resort.
Related
I want to use the response header to determine whether or not to get the response body, the scenario is like this, I need to determine whether or not the session in the response header expires to determine whether or not to continue to download the file.If the session expires, simply cancel the download.
I looked at okhttp's interceptor and felt it didn't meet my requirements.
Interceptor interceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
//拦截
Response originalResponse = chain.proceed(chain.request());
Headers headers = originalResponse.headers();
if (callback != null){
callback.onCallBack(headers,downloadId);
}
//包装响应体并返回
return originalResponse
.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
};
return client.newBuilder()
.addInterceptor(interceptor)
.build();
I have a Xamarin app that talks to an API. There is a certain scenario that happens when talking to the API in that a 401 (Unauthorized) exception is returned. This 401 (Unauthorized) is returned on purpose when the user account is made inactive so that even though the users token is still valid on the app they wouldn't be able to get any data back from the API.
I want to be able log the user out of the app, only when a 401 (Unauthorized) exception is thrown.
My API call looks like this:
public async Task<T> GetAsync<T>(string url)
{
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _authToken?.AccessToken ?? this.GetToken().AccessToken);
var json = await _client.GetStringAsync(url);
return json.Deserialize<T>();
}
When the debugger reaches the var json = await _client.GetStringAsync(url); line a 401 (Unauthorized) exception is correctly thrown.
I want to be able to handle this 401 (Unauthorized) exception and log the user out of the app (preferably with an alert informing them of this).
I'm currently debugging on an Android device so I tried adding the following code to the MainActivity class.
protected override async void OnCreate(Bundle bundle)
{
AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironmentOnUnhandledException;
}
private void AndroidEnvironmentOnUnhandledException(object sender, RaiseThrowableEventArgs e)
{
if(e.Exception.InnerException.GetBaseException().Message == "401 (Unauthorized)")
{
}
}
When the error is thrown I check if its a 401 (Unauthorized). It was here that I thought I would then log the user out of the app but I don't think this is the right direction.
Is there a best practice for handing this type of scenario that I am not aware of yet?
You could try to use try catch to warp var json = await _client.GetStringAsync(url) like the following code.
try
{
var json = await _client.GetStringAsync(url)
}
catch (WebException e)
{
using (WebResponse response = e.Response)
{
HttpWebResponse httpResponse = (HttpWebResponse)response;
Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
using (Stream data = response.GetResponseStream())
using (var reader = new StreamReader(data))
{
// text is the response body
string text = reader.ReadToEnd();
if (text == "401 (Unauthorized)")
{
}
}
}
}
I am currently investigating in WebApi 2 Message Handler and how to implement a server side timeout using cancellation tokens. If a cancellation occurs a OperationCanceledException is thrown and handled in my Message Handler. In this case I return a HttpResponseMessage with a adequate HttpStatusCode (HttpStatusCode.RequestTimeout).
I expected that my consuming client (using postman) retrieves this HttpStatusCode, but instead "Could not get any response" is displayed, thus my client aborts without any additional information. Could someone explain to me whats the deal with this behavior? What am I missing?
See following example code:
public class RequestTimeoutHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
TimeSpan timeout = TimeSpan.FromSeconds(10.0);
using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout))
using (CancellationTokenSource linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token))
{
try
{
return await base.SendAsync(request, linkedCancellationTokenSource.Token);
}
catch (OperationCanceledException e)
{
return new HttpResponseMessage(HttpStatusCode.RequestTimeout);
}
}
return null;
}
}
My Test Controller Method looks as follows:
[Route("testTimeoutAsyncHandleException"), HttpPost]
public IHttpActionResult TestTimeoutAsynchandle(string hugo, CancellationToken ct)
{
for (int i = 0; i < 500; i++)
{
Thread.Sleep(1000); //sleep 1 sec until exception is thown
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
}
return Ok("yes");
}
turns out, using postman was not my best idea. I created a console application using a httpclient and requested my test method. the httpstatus 408 is returned as expected.
I am trying to implement a custom authentication filter in Guice. I receive the token, get the username and realm from the token and then create a Principal. Now I am stuck and I don't know how to set the Principal. It would be nice if I could just set it like this request.setUserPrincipal(principal);, but obviously I can't.
How can I do this?
My doFilter method looks like this:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.length() > 0) {
String token = authorizationHeader.substring("Bearer".length()).trim();
if (token.length() > 0) {
try {
Credentials credentials = securityService.getCredentials(token);
String username = credentials.getUsername();
String realm = credentials.getRealm();
Principal principal = new HttpPrincipal(username, realm);
// request.setUserPrincipal(principal);
LOGGER.info(credentials);
} catch (Exception e) {
LOGGER.error(e);
}
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
The servlet spec section 13.10 says:
The container establishes the caller identity of a request prior to
dispatching the request to the servlet engine. The caller identity
remains unchanged throughout the processing of the request or until
the application sucessfully calls authenticate, login or logout on the
request.
That is the reason why there is no setUserPrincipal.
But there are good news. You can provide your own getUserPrincipal because you can provide your own HttpServletRequest object. Any servlet filter can do it. Look at your code, you are calling the chain method with two parameters: the request and the response. There is no need to pass the same objects that you receive.
The spec even provides you with a helper class: HttpServletRequestWrapper. You just create your own request class as a subclass of the wrapper and override any method that you want, like getUserPrincipal.
I am accessing a web service using WCF. Using WSHttpBinding, Security mode is set Transport (https) and client credential type is Basic. When I try to access the service using the proxy, getting an 401 unauthorized exception.
Here is the Binding
var binding = new WSHttpBinding()
{
UseDefaultWebProxy = true,
Security =
{
Mode = SecurityMode.Transport,
Transport =
{
ClientCredentialType = HttpClientCredentialType.Basic,
},
}
};
Here is the service call
var client = new InternetClient(binding, new EndpointAddress("httpsurl"));
client.ClientCredentials.UserName.UserName = "username";
client.ClientCredentials.UserName.Password = "password";
client.ProcessMessage("somevalue");
When looked into Http headers using Http Analyzer
CONNECT HEADER
(Request-Line):CONNECT somehost.com:443 HTTP/1.1
Host:somehost.com
Proxy-Connection:Keep-Alive
POST HEADER
(Request-Line):POST /Company/1.0 HTTP/1.1
Content-Type:application/soap+xml; charset=utf-8
VsDebuggerCausalityData:uIDPo+voStemjalOv5LtRotFQ7UAAAAAUKLJpa755k6oRwto14BnuE2PDtYKxr9LhfqXFSOo8pEACQAA
Host:somehost.com
Content-Length:898
Expect:100-continue
Connection:Keep-Alive
If you see the header Authorization header is missing
Now my question is why WCF call missing the Authorization header? Am I missing something? . Please ask if you need more information
This is a common problem, but the situation is different from what you think.
It turns out that initially for the 1st request a WCF client that is configured to use HTTP basic authentication will nevertheless send the request without the necessary Authorization header to the server. This is the default behavior of the HttpWebRequest class used by the WCF client.
Normally, the web service server will then return a HTTP 401 Unauthorized response to the WCF client, upon which the latter will resend the message with the Authorization header. This means under normal conditions for HTTP Basic Authentication there will be a a rather useless round trip to the server.
This also explains why the header was missing in your sniffed message. Some Http sniffs possibly don't pass on the 401 response, so the whole exchange gets messed up.
The server round-trip and dependence on the 401 response can be avoided by manually injecting the required Authorization header into every request. See e.g. how to manually inject Authorization header into WCF request
As a slight modification from a previous answer, to support async / await calls, you can actually create a new OperationContext and pass it around on whatever thread you like (as long as it is not shared across concurrent threads as it isn't a thread-safe object)
var client = new MyClient();
client.ClientCredentials.UserName.UserName = "username";
client.ClientCredentials.UserName.Password = "password";
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(client.ClientCredentials.UserName.UserName + ":" + client.ClientCredentials.UserName.Password));
var context = new OperationContext(ormClient.InnerChannel);
using (new OperationContextScope(context))
{
context.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
return await client.SomeMethod();
}
I had the exact same issues. I was able to manually inject the authorization headers by using the following code:
var callcontext = new CAdxCallContext();
callcontext.codeLang = "ENG";
callcontext.poolAlias = "BGRTEST";
var proxy = new CAdxWebServiceXmlCCClient();
proxy.Endpoint.EndpointBehaviors.Add(new CustomEndpoint());
proxy.ClientCredentials.UserName.UserName = "USERNAME"; // Might not benecessary
proxy.ClientCredentials.UserName.Password = "PASSWORD"; // Might not benecessary
string inputXml = "<PARAM>" +
"<GRP ID= \"GRP1\">" +
"<FLD NAME = \"ITMREF\">" + "100001" + "</FLD>" +
"</GRP>" +
"</PARAM>";
CAdxResultXml response;
try
{
response = proxy.run(callcontext, "BGR_SIEPRO", inputXml);
}
catch (TimeoutException timeout)
{
Console.WriteLine(timeout.Message);
// handle the timeout exception.
proxy.Abort();
}
catch (CommunicationException commexception)
{
Console.WriteLine(commexception.Message);
// handle the communication exception.
proxy.Abort();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
proxy.Close();
}
}
public class ClientMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Nothing Here
Console.Write(reply.ToString());
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes("USERNAME" + ":" +
"PASSWORD"));
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
return null;
}
}
public class CustomEndpoint : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// Nothing here
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// Nothing here
}
public void Validate(ServiceEndpoint endpoint)
{
// Nothing here
}
}
Notice the Expect:100-continue in the header. That's the reason for the round trip.
Put this in your web.config and try again:
<system.net>
<settings>
<servicePointManager expect100Continue="false"/>
</settings>
</system.net>
Actually, I was wrong about this question. I did see different behaviour when running HTTP analyzer. While Http anaylzer running, my application crashed after receiving 401 response. When Http analyzer application closed, the above code worked as expected.