I'm trying to set up a WCF service hosted in IIS in ASP.Net compatibility mode that is protected via Forms Authentication and accessed via a .Net User Control in IE. (see Secure IIS hosted WCF service for access via IE hosted user control).
The User Control in IE is needed because it uses a specific third-party control for which there doesn't exist anything comparable in Silverlight or AJAX.
So I need the UserControl to set the authentication and session id cookies in the http request headers before it accesses the WCF service. My approach is to set up a Message inspector that does this.
So I've defined the Message Inspector:
public class CookieInspector : IClientMessageInspector {
public void AfterReceiveReply(ref Message reply, object correlationState) {
}
public object BeforeSendRequest(
ref Message request,
IClientChannel channel) {
HttpRequestMessageProperty messageProperty;
if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name)) {
messageProperty = (HttpRequestMessageProperty) request.Properties[
HttpRequestMessageProperty.Name
];
}
else {
messageProperty = new HttpRequestMessageProperty();
request.Properties.Add(
HttpRequestMessageProperty.Name,
messageProperty
);
}
// Set test headers for now...
messageProperty.Headers.Add(HttpRequestHeader.Cookie, "Bob=Great");
messageProperty.Headers.Add("x-chris", "Beard");
return null;
}
}
and an Endpoint behaviour:
public class CookieBehavior : IEndpointBehavior {
public void AddBindingParameters(
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters) {
}
public void ApplyClientBehavior(
ServiceEndpoint endpoint,
ClientRuntime clientRuntime) {
clientRuntime.MessageInspectors.Add(new CookieInspector());
}
public void ApplyDispatchBehavior(
ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher) {
}
public void Validate(ServiceEndpoint endpoint) {
}
}
and I configure and create my channel and WCF client in code:
var ea = new EndpointAddress("http://.../MyService.svc");
// EDIT: Http cookies can't be set with WSHttpBinding :-(
// var binding = WSHttpBinding();
var binding = new BasicHttpBinding();
// Disable automatically managed cookies (which enables user cookies)
binding.AllowCookies = false;
binding.MaxReceivedMessageSize = 5000000;
binding.ReaderQuotas.MaxStringContentLength = 5000000;
var cf = new ChannelFactory<ITranslationServices>(binding, ea);
cf.Endpoint.Behaviors.Add(new CookieBehavior());
ITranslationServices service = cf.CreateChannel();
However when I look at my request with Fiddler, the http header and cookie aren't set, and I have no clue why. I've read various articles on the Net, Stackoverflow etc that basically say that it should work, but it doesn't. Either I'm missing something obvious, or there's a bug in WCF or something else?
Well I figured it out, if I use a basicHttpBinding instead of a WSHttpBinding it works. No idea why though...
WSHttpBinding may be composed of more than one physical message to one logical message. So when successive physical calls are made, they may not be carrying the cookie appropriately
Related
I am using Azure API , URL getting below error please help on this issue. please share codesnip, how to change in web.config and endpoints.
The HTTP request is unauthorized with client authentication scheme
'Anonymous'. The authentication header received from the server was
'AzureApiManagementKey
realm="https:/azure.azure-api.net/MethodName",name="Ocp-Apim-Subscription-Key",type="header"'.
I know this is a very old question still, my answer would help someone faces the same issue.
The solution is to create a custom endpoint behavior where you add a custom message handler to the binding parameters.
In the custom message handler, please add your request headers. After this, use any of the binding technique (like basichttpsbinding or NetHttpsBinding) with security mode as "Transport" and MessageEncoding as "Text" for creating soap client object. Add custom endpoint behavior to the soap client.
public class CustomEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
{
return new CustomMessageHandler(x);
}));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
public class CustomMessageHandler : DelegatingHandler
{
public CustomMessageHandler(HttpClientHandler handler)
{
InnerHandler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Add("xxxx", "abcde");
return base.SendAsync(request, cancellationToken);
}
}
The console app to consume the service.
static async Task Main(string[] args)
{
var client = GetSOAPClient();
try
{
var result = await client.MyOperation().ConfigureAwait(false);
if(result.Body != null && result.Body.status == "Success")
{
Console.WriteLine(result.Body.myValue);
}
}
catch (Exception ex)
{
Console.WriteLine(ex?.Message);
}
Console.ReadKey();
}
static MyServiceClient GetSOAPClient()
{
NetHttpsBinding binding = new NetHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.MessageEncoding = NetHttpMessageEncoding.Text;
EndpointAddress ea = new EndpointAddress(new Uri("https://myazureurl"));
var client = new MyServiceClient(binding, ea);
client.Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior());
return client;
}
}
This is complaining that your Subscription key is wrong. If you check the response body, it will give you a readable message of what the real problem is. Double check you are entering the correct subscription key for your Azure API access.
You get your subscription key from the Developer Portal under your profile menu. You can see an example of the subscription key being used in this article under the section "Call an operation from the developer portal": https://learn.microsoft.com/en-us/azure/api-management/api-management-get-started
Also, the 'The HTTP request is unauthorized with client authentication scheme 'Anonymous'.' part of the message is a red herring and a separate problem with how responses work.
I've devloped a chat bot application using the Facebook Messenger platform.
I used Spring Boot with embedded Tomcat for the web platform.
The application should run on Amazon aws, open to the WWW, and to be used as a webhook for recieving callbacks from Messenger over https.
I need an advice how to secure the application, so it won't be hacked or flooded with requests that are not coming from Facebook.
I thought to make the application require secured (ssl) connection, but using the "security.require_ssl=true" in application.properties didn't do the work. Perhaps I don't know what is the meaning of this and how to configure it propertly.
Is there a best practice how to block requests which are not https requests? Or a way to block requests which are coming outside Messenger in the application level?
Thank you very much!
EDIT
In the meantime, I blocked requests from other IPs in application layer using the handler interceptor:
#Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer, WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (! (request.getRemoteAddr().equals("173.252.88.66") || request.getRemoteAddr().equals("127.0.0.1")|| request.getRemoteAddr().equals("0:0:0:0:0:0:0:1"))){
logger.warn("Request is not coming from authorized remote address: " + request.getRemoteAddr()+". Rejecting");
response.getWriter().write("Unauthorized Address");
response.setStatus(401);
return false;
} else {
return true;
}
}
}
You should check the X-Hub-signature HTTP header available in the requests sent by Facebook to your webhook URL.
In your case, you may define a filter or interceptor for the verification of the signature. You can also do it in your controller as in the this example I found in RealTimeUpdateController.java from the spring social project.
private boolean verifySignature(String payload, String signature) throws Exception {
if (!signature.startsWith("sha1=")) {
return false;
}
String expected = signature.substring(5);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
SecretKeySpec signingKey = new SecretKeySpec(applicationSecret.getBytes(), HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(payload.getBytes());
String actual = new String(Hex.encode(rawHmac));
return expected.equals(actual);
}
a lot to say so I am sure I will miss some points.
setting SSL is a first good thing but make sure you get a certificate. lets encrypt is a good thing if you dont want to pay for SSL certificate.
Just seeing aws provides an alternative to letsencrypt
Security Group You can see Security Group as something similar to a firewall so you can control which port is opened, external and internal flows.
Look at IAM which control who and how can get access to your AWS account
obvious : change your password. do not let default password for installation you could make on the instance
read some of https://aws.amazon.com/security/security-resources/ to get more information about what you can do
it won't be hacked or flooded with requests
sorry to say but most probably it will be - It does not need to be an advanced hacker to run scanner and scan IPs and check open ports / brute force login etc ...
Thanks to Guy Bouallet help I added the signature check.
I added it in my controller and not in the interceptor, to avoid the problem of How to read data twice in spring which seems a little complicated.
So here is it:
#RequestMapping(path = "/")
public void doHandleCallback(#RequestBody String body, #RequestHeader(value = "X-Hub-Signature") String signature) throws IOException {
if (!verifyRequestSignature(body.getBytes(), signature)){
logger.error ("Signature mismatch.");
throw new MismatchSignatureException(signature);
}
MessengerCallback callback = mapper.readValue(body, MessengerCallback.class);
logger.info("Incoming Callback: " + body );
for (EventData entry : callback.getEntry()) {
for (ReceivedMessagingObject message : entry.getMessaging()) {
if (message.isMessage() || message.isPostback()) {
doHandleMessage(message);
}
else if (message.isDelivery()){
doHandleDelivery(message);
}
}
}
}
private boolean verifyRequestSignature(byte[] payload, String signature) {
if (!signature.startsWith("sha1="))
return false;
String expected = signature.substring(5);
System.out.println("Expected signature: " + expected); //for debugging purposes
String hashResult = HmacUtils.hmacSha1Hex(APP_SECRET.getBytes(), payload);
System.out.println("Calculated signature: " + hashResult);
if (hashResult.equals(expected)) {
return true;
} else {
return false;
}
}
And this is the Exception handling class:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Request Signature mismatch")
public class MismatchSignatureException extends RuntimeException {
private String signature;
public MismatchSignatureException(String signature) {
this.signature = signature;
}
#Override
public String getMessage() {
return "Signature mismatch: " + signature;
}
I am using MassTransit request and response with SignalR. The web site makes a request to a windows service that creates a file. When the file has been created the windows service will send a response message back to the web site. The web site will open the file and make it available for the users to see. I want to handle the scenario where the user closes the web page before the file is created. In that case I want the created file to be emailed to them.
Regardless of whether the user has closed the web page or not, the message handler for the response message will be run. What I want to be able to do is have some way of knowing within the response message handler that the web page has been closed. This is what I have done already. It doesnt work but it does illustrate my thinking. On the web page I have
$(window).unload(function () {
if (event.clientY < 0) {
// $.connection.hub.stop();
$.connection.exportcreate.setIsDisconnected();
}
});
exportcreate is my Hub name. In setIsDisconnected would I set a property on Caller? Lets say I successfully set a property to indicate that the web page has been closed. How do I find out that value in the response message handler. This is what it does now
protected void BasicResponseHandler(BasicResponse message)
{
string groupName = CorrelationIdGroupName(message.CorrelationId);
GetClients()[groupName].display(message.ExportGuid);
}
private static dynamic GetClients()
{
return AspNetHost.DependencyResolver.Resolve<IConnectionManager>().GetClients<ExportCreateHub>();
}
I am using the message correlation id as a group. Now for me the ExportGuid on the message is very important. That is used to identify the file. So if I am going to email the created file I have to do it within the response handler because I need the ExportGuid value. If I did store a value on Caller in my hub for the web page close, how would I access it in the response handler.
Just in case you need to know. display is defined on the web page as
exportCreate.display = function (guid) {
setTimeout(function () {
top.location.href = 'GetExport.ashx?guid=' + guid;
}, 500);
};
GetExport.ashx opens the file and returns it as a response.
Thank you,
Regards Ben
I think a better bet would be to implement proper connection handling. Specifically, have your hub implementing IDisconnect and IConnected. You would then have a mapping of connectionId to document Guid.
public Task Connect()
{
connectionManager.MapConnectionToUser(Context.ConnectionId, Context.User.Name);
}
public Task Disconnect()
{
var connectionId = Context.ConnectionId;
var docId = connectionManager.LookupDocumentId(connectionId);
if (docId != Guid.Empty)
{
var userName = connectionManager.GetUserFromConnectionId(connectionId);
var user = userRepository.GetUserByUserName(userName);
bus.Publish( new EmailDocumentToUserCommand(docId, user.Email));
}
}
// Call from client
public void GenerateDocument(ClientParameters docParameters)
{
var docId = Guid.NewGuid();
connectionManager.MapDocumentIdToConnection(Context.ConnectionId, docId);
var command = new CreateDocumentCommand(docParameters);
command.Correlationid = docId;
bus.Publish(command);
Caller.creatingDocument(docId);
}
// Acknowledge you got the doc.
// Call this from the display method on the client.
// If this is not called, the disconnect method will handle sending
// by email.
public void Ack(Guid docId)
{
connectionManager.UnmapDocumentFromConnectionId(connectionId, docId);
Caller.sendMessage("ok");
}
Of course this is from the top of my head.
I have a web page that uses a WCF service. Multiple users maybe using the web page at any one time and therefore making requests to the WCF service which is on a remote machine.
Each user on the web page gets a unique ID, I want to add this unique ID to the request header of each request made by that user.
So far I have created the following code which correctly adds a header to the WCF message.
public class HeaderIdPusher : IClientMessageInspector
{
private static readonly string _balancerKey = "balancerId";
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Guid userId = Guid.NewGuid();
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[_balancerKey]))
{
httpRequestMessage.Headers[_balancerKey] = userId.ToString();
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(_balancerKey, userId.ToString());
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
However I am no stuck because I can't get the ID to persist between requests. You can see here that at the moment I am generating an ID for each request, however I can't store this in the Session of the page the user is on because the HttpContext.Current is null. Is there another way of storing this? Is there another way of passing in the HttpContext of the user on my web page?
The problem is discussed here:
http://social.msdn.microsoft.com/forums/en-US/wcf/thread/27896125-b61e-42bd-a1b0-e6da5c23e6fc
Essentially WCF doesn't have sessions, as you could pass anything you wanted as a parameter (in this case, your Unique ID) and handle it any way you wanted in your implementation.
After much hacking I found a solution, it isn't great but it works.
In the ASP.NET page before I create the WCF service instance I create an address header and endpoint:
AddressHeader header = AddressHeader.CreateAddressHeader("MyKey", "http://www.w3.org/2005/08/addressing", "MyValue");
EndpointAddress endpoint = new EndpointAddress(new Uri("http://www.myservice.com/service"), header);
Then I create an instance of the service passing in the endpoint:
using (WcfService service = new WcfService(_configName,endpoint ))
{
}
This gets the data into the WCF service, then in the HeaderIdPusher : IClientMessageInspector detailed above I pull the header value out:
public class HeaderIdPusher : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
string id = "Not found";
if(channel.RemoteAddress.Headers.Any(x=>x.Name == "MyKey"))
{
id = channel.RemoteAddress.Headers.First(x => x.Name == "MyKey").GetValue<string>();
}
This solution isn't ideal and it puts extra data into the SOAP message but it is the only way I have found of sharing data from the ASP.NET page with the WCF process.
I am a GWT noob but am working with someone else who is more advanced than I, and we cannot figure out why a cookie being returned by the server as a Set-Cookie HTTP header is not actually being set in the browser.
I wrote a server using Tomcat that has an authentication call. I wrote a dummy website all in HTML that uses web forms to send a request to the server with the authentication information and receives a response that contains a Set-Cookie header. This all works. It then has a second button in a different form on the same page that sends a different request to my server with some form data, and the browser automatically injects the cookie into the header as expected. Therefore, the server, for the second call, can pull the cookie header out of the request and authenticate the request. This all works and is great.
Now, for the test GWT application we have developed, I have used the code that is automatically generated when a new GWT application is developed (no AppEngine) and modified it in the following ways on the client side's EntryPoint class. I removed the TextBox for entering my name and the GWT RPC calls. I modified MyHandler so that it no longer implemented KeyPressedListener or whatever and does implement RequestCallback. I edited the contents of the onClick to create a new RequestBuilder that sends a POST with the authentication information. So far, this all works as I can watch the logs on my server and it receives the request, processes it, and places the authentication cookie in the response. Using Firebug, I can see that the response contains the Set-Cookie header with the necessary cookie information. However, the browser never actually saves this information. Unsurprisingly, a subsequent call to the server doesn't include the cookie.
GWT is just compiled into JavaScript when deployed, correct? And JavaScript can't inject itself between the HTTP response and the browser can it? I have checked the Response object that is a parameter to the onResponseReceived() call from the RequestCallback interface, and it doesn't contain any method to get access to the cookie except through the getHeaders() call. I have dumped the results of this call, though, and it doesn't exist there. Anyway, the browser should at least be getting access to the HTTP header before the code and should be grabbing and setting the cookie values before handing the code to GWT. Not only am I new to GWT, I am new to most HTTP client-side development, but am I really that far off track?
Thank you,
John
Edit:
Here is the code I ended up with. I didn't change anything else in the project.
public void onModuleLoad() {
final Button loginButton = new Button("Login");
final Button requestBuilderButton = new Button("Campaign Read");
final Label errorLabel = new Label();
// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("sendButtonContainer").add(loginButton);
RootPanel.get("sendButtonContainer").add(requestBuilderButton);
RootPanel.get("errorLabelContainer").add(errorLabel);
// Create the popup dialog box
final DialogBox dialogBox = new DialogBox();
dialogBox.setText("Remote Procedure Call");
dialogBox.setAnimationEnabled(true);
final Button closeButton = new Button("Close");
// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");
final Label textToServerLabel = new Label();
final HTML serverResponseLabel = new HTML();
VerticalPanel dialogVPanel = new VerticalPanel();
dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("<b>Sending name to the server:</b>"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("<br><b>Server replies:</b>"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);
dialogBox.setWidget(dialogVPanel);
// Add a handler to close the DialogBox
closeButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
dialogBox.hide();
}
});
// Create a handler for the sendButton and nameField
class LoginHandler implements ClickHandler, RequestCallback {
/**
* Fired when the user clicks on the sendButton.
*/
public void onClick(ClickEvent event) {
dialogBox.show();
serverResponseLabel.setText(Cookies.getCookie("auth_token"));
final String url = "http://localhost:8080/app/user/auth_token";
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
StringBuilder parameters = new StringBuilder();
parameters.append("user=username&password=password&client=gwt");
try {
builder.sendRequest(URL.encode(parameters.toString()), this);
}
catch(RequestException e) {
serverResponseLabel.setText(e.toString());
}
}
public void onError(Request request, Throwable exception) {
serverResponseLabel.setText("Failure.");
}
public void onResponseReceived(Request request, Response response) {
textToServerLabel.setText(Integer.toString(response.getStatusCode()));
serverResponseLabel.setText(serverResponseLabel.getText() + Cookies.getCookie("auth_token"));
}
};
class CampaignReadHandler implements ClickHandler, RequestCallback {
public void onClick(ClickEvent event) {
dialogBox.show();
final String url = "http://localhost:8080/app/campaign/read";
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
StringBuilder parameters = new StringBuilder();
parameters.append("output_format=short&client=gwt&campaign_urn_list=urn:andwellness:nih");
try {
builder.sendRequest(URL.encode(parameters.toString()), this);
}
catch(RequestException e) {
serverResponseLabel.setText(e.toString());
}
}
public void onError(Request request, Throwable exception) {
serverResponseLabel.setText("Failure.");
}
public void onResponseReceived(Request request, Response response) {
textToServerLabel.setText(Integer.toString(response.getStatusCode()));
serverResponseLabel.setText(response.getText());
}
};
// Add a handler to send the name to the server
LoginHandler loginHandler = new LoginHandler();
loginButton.addClickHandler(loginHandler);
CampaignReadHandler campaignReadHandler = new CampaignReadHandler();
requestBuilderButton.addClickHandler(campaignReadHandler);
}
This is the expected behavior of browsers: http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method (GWT's Response#getHeaders simply calls getAllResponseHeaders and parses the string).
If you want to get cookies, you have to use the cookies object (Cookies class in GWT); which obviously filters out httponly cookies.
If you are using RequestBuilder to contact the RPC servlet that may be the problem. Especially if you are using a different host in your request, than what you have in your browser.
Say navigating to http://localhost/app
But your RequestBuilder builds a request for http://machinename/app/servlet.
If you are just using RPC without RequestBuilder you shouldn't have these problems.
As well if you are using RequestBuilder you may have to manually provide the cookies via setting that particular header
In browser client development cookies are handled on a host name basis.