Response.Redirect avec POST au lieu de Get?

248

Nous avons l'obligation de prendre une soumission de formulaire et d'enregistrer certaines données, puis de rediriger l'utilisateur vers une page hors site, mais lors de la redirection, nous devons «soumettre» un formulaire avec POST, pas GET.

J'espérais qu'il y avait un moyen facile d'accomplir cela, mais je commence à penser qu'il n'y en a pas. Je pense que je dois maintenant créer une autre page simple, avec juste le formulaire que je veux, y rediriger, remplir les variables du formulaire, puis faire un appel body.onload à un script qui appelle simplement document.forms [0] .submit ( );

Quelqu'un peut-il me dire s'il existe une alternative? Nous pourrions avoir besoin de modifier cela plus tard dans le projet, et cela pourrait devenir un peu compliqué, donc s'il y avait un moyen facile, nous pourrions faire tout cela sans autre page dépendante, ce serait fantastique.

Quoi qu'il en soit, merci pour toutes les réponses.

Matt Dawdy
la source
En PHP, vous pouvez envoyer des données POST avec cURL. Existe-t-il quelque chose de comparable pour .NET?
Brian Warshaw
Je pense que c'est la réponse facile que vous cherchiez. Je ne pouvais pas croire à quel point c'était ingénieux ... stackoverflow.com/a/6062248/110549
JoeCool
@BrianWarshaw Je trouve System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/… très intuitif et rapide à utiliser.
Stoyan Dimov

Réponses:

228

Pour ce faire, vous devez comprendre le fonctionnement des redirections HTTP. Lorsque vous utilisez Response.Redirect(), vous envoyez une réponse (au navigateur qui a fait la demande) avec le code d'état HTTP 302 , qui indique au navigateur où aller ensuite. Par définition, le navigateur le fera via une GETdemande, même si la demande d'origine était un POST.

Une autre option consiste à utiliser le code d'état HTTP 307 , qui spécifie que le navigateur doit effectuer la demande de redirection de la même manière que la demande d'origine, mais pour inviter l'utilisateur avec un avertissement de sécurité. Pour ce faire, vous écririez quelque chose comme ceci:

public void PageLoad(object sender, EventArgs e)
{
    // Process the post on your side   

    Response.Status = "307 Temporary Redirect";
    Response.AddHeader("Location", "http://example.com/page/to/post.to");
}

Malheureusement, cela ne fonctionnera pas toujours. Différents navigateurs l'implémentent différemment , car ce n'est pas un code d'état commun.

Hélas, contrairement aux développeurs Opera et FireFox, les développeurs IE n'ont jamais lu la spécification, et même le dernier IE7, le plus sécurisé, redirigera la requête POST du domaine A vers le domaine B sans aucun avertissement ni dialogue de confirmation! Safari agit également de manière intéressante, bien qu'il n'ouvre pas de boîte de dialogue de confirmation et n'effectue pas la redirection, il jette les données POST, changeant efficacement la redirection 307 en la 302 plus courante.

Donc, pour autant que je sache, la seule façon d'implémenter quelque chose comme ça serait d'utiliser Javascript. Il y a deux options auxquelles je peux penser du haut de ma tête:

  1. Créez le formulaire et actionfaites pointer son attribut vers le serveur tiers. Ensuite, ajoutez un événement de clic au bouton d'envoi qui exécute d'abord une demande AJAX à votre serveur avec les données, puis autorise l'envoi du formulaire au serveur tiers.
  2. Créez le formulaire à publier sur votre serveur. Lorsque le formulaire est soumis, affichez à l'utilisateur une page contenant un formulaire contenant toutes les données que vous souhaitez transmettre, le tout dans des entrées masquées. Montrez juste un message comme "Rediriger ...". Ensuite, ajoutez un événement javascript à la page qui soumet le formulaire au serveur tiers.

Des deux, je choisirais la seconde, pour deux raisons. Premièrement, il est plus fiable que le premier car Javascript n'est pas nécessaire pour qu'il fonctionne; pour ceux qui ne l'ont pas activé, vous pouvez toujours rendre visible le bouton d'envoi du formulaire masqué et leur demander d'appuyer dessus si cela prend plus de 5 secondes. Deuxièmement, vous pouvez décider quelles données seront transmises au serveur tiers; si vous utilisez simplement traiter le formulaire au fur et à mesure, vous transmettrez toutes les données de publication, ce qui n'est pas toujours ce que vous voulez. Idem pour la solution 307, en supposant qu'elle fonctionne pour tous vos utilisateurs.

J'espère que cela t'aides!

tghw
la source
1
Veuillez remplacer "ajouter un événement de clic au bouton d'envoi" par "ajouter un événement d'envoi au formulaire". Il existe plusieurs méthodes pour soumettre un formulaire, par exemple en appuyant sur Entrée avec n'importe quelle saisie de texte ciblée, et encore moins par une soumission programmatique.
temoto
122

Vous pouvez utiliser cette approche:

Response.Clear();

StringBuilder sb = new StringBuilder();
sb.Append("<html>");
sb.AppendFormat(@"<body onload='document.forms[""form""].submit()'>");
sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
// Other params go here
sb.Append("</form>");
sb.Append("</body>");
sb.Append("</html>");

Response.Write(sb.ToString());

Response.End();

Comme résultat juste après client obtenir tous les fichiers html du serveur l'événement OnLoad lieu que les déclencheurs forment et soumettre des données à afficher toutes PostBackUrl définies.

Pavlo Neiman
la source
9
+1 (je vous en ajouterais plus si je le pouvais). Telle est la réponse. Éloquent et précis. Vous avez même inclus tout le code pour le faire au lieu de parler du haut de votre tête. J'ai utilisé cela dans un iframe sur ma page aspx et cela a rendu tout parfaitement - pas de réécriture d'URL. Excellent travail! CONSEIL pour ceux qui utilisent iframe: je pointe mon iframe vers une autre page aspx qui exécute ensuite ce code.
MikeTeeVee
1
Ça marche! La seule mauvaise chose que je vois, c'est que si vous appuyez sur le bouton de retour du navigateur, il fait à nouveau la demande, de sorte que vous ne pouvez plus vraiment atteindre la page précédente. Y a-t-il une solution de contournement à cela?
Mt. Schneiders
4
Il y a une raison pour laquelle la méthode prise en charge pour ce faire utilise un 307. Les actions POST sont destinées à être des transactions idempotentes. Cette réponse n'est qu'un piratage qui fonctionne et qui peut être très facilement bloqué par les navigateurs à l'avenir.
Sam Rueby
2
@sam bon point. Comme contre-argument: selon la première réponse, le développeur IE n'a même pas lu environ 307. Ceux qui l'ont lu l'ont implémenté à tort. Cela signifie que 307 est clairement déroutant (en supposant que les développeurs de navigateurs sont intelligents) et sujet à des erreurs d'interprétation. L'approche ci-dessus est claire, du moins pour moi et elle fonctionne dans tous les navigateurs passés et présents. Pas trop inquiets pour l'avenir car nous, développeurs, combattons le passé (lire IE7) et le présent aujourd'hui. À mon humble avis, étant donné que tout le monde l'a correctement, il doit le conserver tel quel. Quelle serait la raison de le bloquer à l'avenir?
so_mv
2
Comment cela n'est-il pas effectivement le même que la deuxième option de TGHW? N'êtes-vous pas aussi potentiellement demander à quelqu'un imprudent d'introduire une vulnérabilité XSS?
Scott
33

HttpWebRequest est utilisé pour cela.

Lors de la publication, créez un HttpWebRequest à votre tiers et publiez les données du formulaire, puis une fois cela fait, vous pouvez Response.Redirect où vous le souhaitez.

Vous obtenez l'avantage supplémentaire que vous n'avez pas à nommer tous vos contrôles serveur pour créer le formulaire tiers, vous pouvez faire cette traduction lors de la création de la chaîne POST.

string url = "3rd Party Url";

StringBuilder postData = new StringBuilder();

postData.Append("first_name=" + HttpUtility.UrlEncode(txtFirstName.Text) + "&");
postData.Append("last_name=" + HttpUtility.UrlEncode(txtLastName.Text));

//ETC for all Form Elements

// Now to Send Data.
StreamWriter writer = null;

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";                        
request.ContentLength = postData.ToString().Length;
try
{
    writer = new StreamWriter(request.GetRequestStream());
    writer.Write(postData.ToString());
}
finally
{
    if (writer != null)
        writer.Close();
}

Response.Redirect("NewPage");

Cependant, si vous avez besoin que l'utilisateur voie la page de réponse de ce formulaire, votre seule option est d'utiliser Server.Transfer, et cela peut ou peut ne pas fonctionner.

FlySwat
la source
est-ce que je vais utiliser si je veux un formulaire supplémentaire que le formulaire asp.net. J'ai besoin de publier des données sur une URL externe qui est pour le paiement sécurisé 3D, puis j'ai besoin d'obtenir des informations retournées à partir de la demande. Est-ce la façon de procéder? Merci
Barbaros Alp
Si vous avez besoin que l'utilisateur voie la réponse, vous pouvez simplement renvoyer le contenu et les en-têtes appropriés. Vous devrez peut-être modifier certains aspects du résultat en fonction de l'utilisation des ressources relatives, mais c'est certainement possible.
Thomas S. Trias, du
6
L'utilisateur peut avoir des données de cookies qui ne seraient pas transmises via cette méthode, car cela se produit depuis le serveur.
Scott
7

Cela devrait vous faciliter la vie. Vous pouvez simplement utiliser facilement la méthode Response.RedirectWithData (...) dans votre application Web.

Imports System.Web
Imports System.Runtime.CompilerServices

Module WebExtensions

    <Extension()> _
    Public Sub RedirectWithData(ByRef aThis As HttpResponse, ByVal aDestination As String, _
                                ByVal aData As NameValueCollection)
        aThis.Clear()
        Dim sb As StringBuilder = New StringBuilder()

        sb.Append("<html>")
        sb.AppendFormat("<body onload='document.forms[""form""].submit()'>")
        sb.AppendFormat("<form name='form' action='{0}' method='post'>", aDestination)

        For Each key As String In aData
            sb.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, aData(key))
        Next

        sb.Append("</form>")
        sb.Append("</body>")
        sb.Append("</html>")

        aThis.Write(sb.ToString())

        aThis.End()
    End Sub

End Module
ZooZ
la source
6

Une nouveauté d'ASP.Net 3.5 est cette propriété "PostBackUrl" des boutons ASP. Vous pouvez le définir à l'adresse de la page que vous souhaitez publier directement, et lorsque vous cliquez sur ce bouton, au lieu de le renvoyer sur la même page comme d'habitude, il publie à la place sur la page que vous avez indiquée. Pratique. Assurez-vous que UseSubmitBehavior est également défini sur TRUE.

Mike K
la source
4

J'ai pensé qu'il pourrait être intéressant de partager que Heroku le fait avec son fournisseur SSO to Add-on

Un exemple de comment cela fonctionne peut être vu dans la source de l'outil "kensa":

https://github.com/heroku/kensa/blob/d4a56d50dcbebc2d26a4950081acda988937ee10/lib/heroku/kensa/post_proxy.rb

Et peut être vu dans la pratique si vous désactivez javascript. Exemple de source de page:

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Heroku Add-ons SSO</title>
  </head>

  <body>
    <form method="POST" action="https://XXXXXXXX/sso/login">

        <input type="hidden" name="email" value="XXXXXXXX" />

        <input type="hidden" name="app" value="XXXXXXXXXX" />

        <input type="hidden" name="id" value="XXXXXXXX" />

        <input type="hidden" name="timestamp" value="1382728968" />

        <input type="hidden" name="token" value="XXXXXXX" />

        <input type="hidden" name="nav-data" value="XXXXXXXXX" />

    </form>

    <script type="text/javascript">
      document.forms[0].submit();
    </script>
  </body>
</html>
Jacob
la source
3

PostbackUrl peut être défini sur votre bouton asp pour publier sur une autre page.

si vous devez le faire dans codebehind, essayez Server.Transfer.

Jimmy
la source
2

@Mat,

Vous pouvez toujours utiliser HttpWebRequest, puis diriger la réponse que vous recevez vers la réponse de flux de sortie réelle, cela servirait la réponse à l'utilisateur. Le seul problème est que toutes les URL relatives seraient rompues.

Pourtant, cela peut fonctionner.

FlySwat
la source
2

Voici ce que je ferais:

Mettez les données dans un formulaire standard (sans attribut runat = "server") et définissez l'action du formulaire à publier sur la page hors site cible. Avant de soumettre, je soumettais les données à mon serveur à l' aide d'un XmlHttpRequest et j'analysais la réponse. Si la réponse signifie que vous devriez aller de l'avant avec le POSTing hors site, je (le JavaScript) procéderais au post, sinon je redirigerais vers une page de mon site

Andrei Rînea
la source
Cela fonctionne, mais il est important de noter que vous perdez toutes les fonctionnalités côté serveur ASP.NET.
senfo
2

En PHP, vous pouvez envoyer des données POST avec cURL. Existe-t-il quelque chose de comparable pour .NET?

Oui, HttpWebRequest, voir mon article ci-dessous.

FlySwat
la source
2

La méthode GET (et HEAD) ne doit jamais être utilisée pour faire quoi que ce soit qui a des effets secondaires. Un effet secondaire peut être la mise à jour de l'état d'une application Web ou le chargement de votre carte de crédit. Si une action a des effets secondaires, une autre méthode (POST) doit être utilisée à la place.

Ainsi, un utilisateur (ou son navigateur) ne devrait pas être tenu responsable de quelque chose fait par un GET. Si un effet secondaire nocif ou coûteux se produisait à la suite d'un GET, ce serait la faute de l'application Web, pas de l'utilisateur. Selon la spécification, un agent utilisateur ne doit pas suivre automatiquement une redirection, sauf s'il s'agit d'une réponse à une demande GET ou HEAD.

Bien sûr, de nombreuses demandes GET ont des effets secondaires, même si elles ne font que s'ajouter à un fichier journal. L'important est que l'application, et non l'utilisateur, soit tenue pour responsable de ces effets.

Les sections pertinentes de la spécification HTTP sont 9.1.1 et 9.1.2 et 10.3 .

erickson
la source
1

Je suggère de créer un HttpWebRequest pour exécuter votre POST par programme, puis rediriger après avoir lu la réponse, le cas échéant.

Ben Griswold
la source
1

Code copiable-collable basé sur la méthode de Pavlo Neyman

RedirectPost (chaîne url, T bodyPayload) et GetPostData () sont pour ceux qui veulent simplement vider des données fortement typées dans la page source et les récupérer dans la cible. Les données doivent être sérialisables par NewtonSoft Json.NET et vous devez bien sûr référencer la bibliothèque.

Copiez-collez simplement dans vos pages ou, mieux encore, classe de base pour vos pages et utilisez-le n'importe où dans votre application.

Mon cœur va à vous tous qui devez encore utiliser les formulaires Web en 2019 pour une raison quelconque.

        protected void RedirectPost(string url, IEnumerable<KeyValuePair<string,string>> fields)
        {
            Response.Clear();

            const string template =
@"<html>
<body onload='document.forms[""form""].submit()'>
<form name='form' action='{0}' method='post'>
{1}
</form>
</body>
</html>";

            var fieldsSection = string.Join(
                    Environment.NewLine,
                    fields.Select(x => $"<input type='hidden' name='{HttpUtility.UrlEncode(x.Key)}' value='{HttpUtility.UrlEncode(x.Value)}'>")
                );

            var html = string.Format(template, HttpUtility.UrlEncode(url), fieldsSection);

            Response.Write(html);

            Response.End();
        }

        private const string JsonDataFieldName = "_jsonData";

        protected void RedirectPost<T>(string url, T bodyPayload)
        {
            var json = JsonConvert.SerializeObject(bodyPayload, Formatting.Indented);
            //explicit type declaration to prevent recursion
            IEnumerable<KeyValuePair<string, string>> postFields = new List<KeyValuePair<string, string>>()
                {new KeyValuePair<string, string>(JsonDataFieldName, json)};

            RedirectPost(url, postFields);

        }

        protected T GetPostData<T>() where T: class 
        {
            var urlEncodedFieldData = Request.Params[JsonDataFieldName];
            if (string.IsNullOrEmpty(urlEncodedFieldData))
            {
                return null;// default(T);
            }

            var fieldData = HttpUtility.UrlDecode(urlEncodedFieldData);

            var result = JsonConvert.DeserializeObject<T>(fieldData);
            return result;
        }
Zar Shardan
la source
0

En règle générale, tout ce dont vous aurez besoin est de transporter un état entre ces deux demandes. Il y a en fait une façon vraiment funky de faire cela qui ne repose pas sur JavaScript (pensez <noscript />).

Set-Cookie: name=value; Max-Age=120; Path=/redirect.html

Avec ce cookie là, vous pouvez dans la demande suivante à /redirect.html récupérer les informations nom = valeur, vous pouvez stocker tout type d'informations dans cette chaîne de paire nom / valeur, jusqu'à 4K de données (limite de cookie typique). Bien sûr, vous devez éviter cela et stocker à la place les codes d'état et les bits d'indicateur.

Dès réception de cette demande, vous répondez en retour par une demande de suppression de ce code d'état.

Set-Cookie: name=value; Max-Age=0; Path=/redirect.html

Mon HTTP est un peu rouillé J'ai parcouru les RFC2109 et RFC2965 pour comprendre à quel point c'est vraiment fiable, de préférence je voudrais que le cookie fasse un aller-retour exactement une fois mais cela ne semble pas être possible, aussi, les cookies tiers peut être un problème pour vous si vous déménagez dans un autre domaine. C'est toujours possible, mais pas aussi indolore que lorsque vous faites des choses dans votre propre domaine.

Le problème ici est la concurrence, si un utilisateur avancé utilise plusieurs onglets et parvient à entrelacer quelques demandes appartenant à la même session (ce qui est très peu probable, mais pas impossible), cela peut entraîner des incohérences dans votre application.

C'est la façon <noscript /> de faire des allers-retours HTTP sans URL sans signification et JavaScript

Je fournis ce code comme un prof de concept: si ce code est exécuté dans un contexte que vous ne connaissez pas, je pense que vous pouvez déterminer quelle partie est quoi.

L'idée est que vous appelez Relocate avec un certain état lorsque vous redirigez, et l'URL que vous avez déplacé appelle GetState pour obtenir les données (le cas échéant).

const string StateCookieName = "state";

static int StateCookieID;

protected void Relocate(string url, object state)
{
    var key = "__" + StateCookieName + Interlocked
        .Add(ref StateCookieID, 1).ToInvariantString();

    var absoluteExpiration = DateTime.Now
        .Add(new TimeSpan(120 * TimeSpan.TicksPerSecond));

    Context.Cache.Insert(key, state, null, absoluteExpiration,
        Cache.NoSlidingExpiration);

    var path = Context.Response.ApplyAppPathModifier(url);

    Context.Response.Cookies
        .Add(new HttpCookie(StateCookieName, key)
        {
            Path = path,
            Expires = absoluteExpiration
        });

    Context.Response.Redirect(path, false);
}

protected TData GetState<TData>()
    where TData : class
{
    var cookie = Context.Request.Cookies[StateCookieName];
    if (cookie != null)
    {
        var key = cookie.Value;
        if (key.IsNonEmpty())
        {
            var obj = Context.Cache.Remove(key);

            Context.Response.Cookies
                .Add(new HttpCookie(StateCookieName)
                { 
                    Path = cookie.Path, 
                    Expires = new DateTime(1970, 1, 1) 
                });

            return obj as TData;
        }
    }
    return null;
}
John Leidegren
la source