Get. 대신 POST로 리디렉션?


248

양식을 제출하고 일부 데이터를 저장 한 다음 사용자를 페이지 외부로 리디렉션해야하지만 리디렉션에서는 GET이 아닌 POST로 양식을 "제출"해야합니다.

나는 이것을 달성하는 쉬운 방법이 있기를 바랐지만, 그렇지 않다고 생각하기 시작했다. 이제 원하는 양식만으로 간단한 다른 페이지를 작성하고, 리디렉션하고, 양식 변수를 채운 다음 document.forms [0] .submit ( );

대안이 있는지 아는 사람이 있습니까? 우리는 나중에 프로젝트에서 이것을 조정해야 할 수도 있고, 다소 복잡해 질 수도 있습니다. 따라서 쉬운 경우에는 페이지가 아닌 다른 모든 페이지에 환상적 일 수 있습니다.

어쨌든 모든 응답에 감사드립니다.


PHP에서는 cURL을 사용하여 POST 데이터를 보낼 수 있습니다. .NET과 비슷한 것이 있습니까?
Brian Warshaw

나는 이것이 당신이 찾고있는 쉬운 대답이라고 생각합니다. 나는 그것이 얼마나 독창적 인 ... 믿을 수 없었다 stackoverflow.com/a/6062248/110549
JoeCool

@BrianWarshaw System.Net.Http.HttpClient msdn.microsoft.com/en-us/library/… 매우 직관적이고 사용하기 쉽다는 것을 알았습니다 .
Stoyan Dimov 2016 년

답변:


228

이렇게하려면 HTTP 리디렉션 작동 방식을 이해해야합니다. 사용할 때Response.Redirect() HTTP 상태 코드 302 로 응답을 보내 (브라우저에게 요청) 브라우저에 다음으로 이동할 위치를 알려줍니다. 정의에 따르면 브라우저는 GET원래 요청이 이었더라도 요청을 통해이를 수행 POST합니다.

다른 옵션은 HTTP 상태 코드 307 하는 것인데, 이는 브라우저가 원래 요청과 동일한 방식으로 경로 재 지정 요청을 수행하도록 지정하지만 사용자에게 보안 경고를 표시하도록 지정합니다. 그렇게하려면 다음과 같이 작성하십시오.

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");
}

불행히도, 이것이 항상 작동하지는 않습니다. 다른 브라우저 는 일반적인 상태 코드가 아니기 때문에 이것을 다르게 구현 합니다.

아아, Opera 및 FireFox 개발자와 달리 IE 개발자는 사양을 읽지 않았으며 가장 안전한 최신 IE7조차도 경고 또는 확인 대화 상자없이 POST 요청을 도메인 A에서 도메인 B로 리디렉션합니다! Safari는 재미있는 방식으로 작동하지만 확인 대화 상자를 표시하지 않고 리디렉션을 수행하지만 POST 데이터를 버리고 307 리디렉션을 더 일반적인 302로 효과적으로 변경합니다.

그래서 내가 아는 한, 이와 같은 것을 구현하는 유일한 방법은 Javascript를 사용하는 것입니다. 머리 꼭대기에서 생각할 수있는 두 가지 옵션이 있습니다.

  1. 양식을 작성하고 action 속성이 타사 서버를 가리 키도록합니다. 그런 다음 제출 단추에 클릭 이벤트를 추가하여 먼저 데이터와 함께 서버에 AJAX 요청을 실행 한 다음 양식을 타사 서버에 제출할 수 있습니다.
  2. 서버에 게시 할 양식을 작성하십시오. 양식이 제출되면 사용자에게 전달하려는 모든 데이터가 포함 된 양식이있는 페이지를 숨겨진 입력으로 표시하십시오. "리 디렉팅 ..."과 같은 메시지를 표시하십시오. 그런 다음, 서드 파티 서버에 양식을 제출하는 페이지에 javascript 이벤트를 추가하십시오.

두 가지 중에서 두 가지 이유로 두 번째를 선택합니다. 첫째, Javascript가 작동하는 데 필요하지 않기 때문에 첫 번째보다 더 안정적입니다. 사용하도록 설정하지 않은 사용자의 경우 숨겨진 양식의 제출 버튼을 항상 표시하고 5 초 이상 걸리는 경우이를 누르도록 지시 할 수 있습니다. 둘째, 타사 서버로 전송할 데이터를 결정할 수 있습니다. 사용하는 양식 만 처리하면 모든 게시물 데이터가 전달되므로 항상 원하는 것은 아닙니다. 모든 사용자에게 효과적이라고 가정하면 307 솔루션과 동일합니다.

도움이 되었기를 바랍니다!


1
"제출 버튼에 클릭 이벤트 추가"를 "양식에 제출 이벤트 추가"로 변경하십시오. 양식을 제출하는 방법은 여러 가지가 있습니다. 예를 들어 프로그래밍 방식의 제출은 물론 텍스트 입력에 초점을 둔 상태에서 Enter를 누르십시오.
temoto

122

이 aproach를 사용할 수 있습니다 :

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

결과적으로 클라이언트가 서버에서 모든 HTML을 가져온 직후 이벤트 onload 가 발생하여 양식 제출을 트리거하고 모든 데이터를 정의 된 postbackUrl에 게시합니다.


9
+1 (가능한 경우 더 많은 것을 더할 것입니다). 이것이 답입니다. 웅변과 요점. 심지어 머리 위로 말하지 않고 모든 코드를 포함 시켰습니다. 나는 이것을 aspx 페이지의 iframe에서 사용했으며 URL을 다시 쓰지 않고 모든 것을 완벽하게 렌더링했습니다. 잘했습니다! iframe을 사용하는 사람들을위한 팁 : iframe을 다른 aspx 페이지로 가리킨 다음이 코드를 실행합니다.
MikeTeeVee

1
작동합니다! 내가 볼 수있는 유일한 나쁜 점은 브라우저의 뒤로 버튼을 누르면 다시 요청을 수행하므로 더 이상 이전 페이지에 도달 할 수 없다는 것입니다. 이에 대한 해결 방법이 있습니까?
마운트 Schneiders

4
이를 수행하기 위해 지원되는 방법이 307을 사용하는 이유가 있습니다. POST 조치는 dem 등원 트랜잭션입니다. 이 답변은 해킹에 불과하며 향후 브라우저에서 매우 쉽게 차단할 수 있습니다.
Sam Rueby

2
@sam 좋은 지적. 반론으로 : IE 개발자는 심지어 307에 대해 읽지 못했습니다. 답변을 읽는 다른 사람들은 그것을 잘못 구현했습니다. 이는 307이 명확하게 똑똑하고 (브라우저 개발자가 똑똑하다고 가정) 해석 오류를 일으키기 쉽다는 것을 의미합니다. 위의 접근 방식은 적어도 나에게는 분명하며 과거와 현재의 모든 브라우저에서 작동합니다. 우리 개발자들이 과거 (IE7 읽기)와 현재와의 싸움에서 미래에 대해 걱정하지 않아도됩니다. IMHO, 모든 사람들이 올바르게 이해했기 때문에 그대로 유지해야합니다. 앞으로 그것을 막는 근거는 무엇입니까?
so_mv

2
이것이 TGHW의 두 번째 옵션과 어떻게 동일하지 않습니까? XSS 취약점을 부주의하게 소개하는 사람을 잠재적으로 요구하지 않습니까?
Scott

33

이를 위해 HttpWebRequest가 사용됩니다.

포스트 백에서 HttpWebRequest를 써드 파티에 작성하고 양식 데이터를 게시 한 후 완료되면 원하는 위치에 Response.Redirect 할 수 있습니다.

타사 양식을 만들기 위해 모든 서버 컨트롤의 이름을 지정할 필요가 없다는 추가 이점이 있습니다. 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");

그러나 사용자가이 양식에서 응답 페이지를 보도록하려면 Server.Transfer를 사용하는 것이 유일한 옵션이며 작동하지 않을 수도 있습니다.


asp.net 양식보다 추가 양식을 원할 경우 사용할 것입니까? 3d 보안 결제를위한 외부 URL에 일부 데이터를 게시해야합니다. 요청에서 정보를 반환해야합니다. 이것이 이것을하는 방법입니까? 감사합니다
Barbaros Alp

사용자가 응답을 볼 필요가있는 경우 컨텐츠와 적절한 헤더를 다시 보낼 수 있습니다. 상대 자원의 사용에 따라 결과의 일부 측면을 수정해야 할 수도 있지만 확실히 가능합니다.
Thomas S. Trias

6
서버에서 발생하는 쿠키 데이터가이 방법을 통해 전송되지 않을 수 있습니다.
Scott

7

이것은 인생을 훨씬 쉽게 만들어 줄 것입니다. 웹 응용 프로그램에서 Response.RedirectWithData (...) 메서드를 쉽게 사용할 수 있습니다.

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

6

ASP.Net 3.5의 새로운 기능은 ASP 버튼의 "PostBackUrl"속성입니다. 직접 게시하려는 페이지의 주소로 설정할 수 있으며, 버튼을 클릭하면 일반 페이지와 같은 페이지에 다시 게시하는 대신 지정한 페이지에 게시됩니다. 능숙한. UseSubmitBehavior도 TRUE로 설정되어 있는지 확인하십시오.


4

Heroku가 애드온 제공 업체에 대한 SSO와 함께이 작업을 수행한다는 사실을 공유하는 것이 흥미로울 수 있습니다.

작동 방식의 예는 "kensa"도구의 소스에서 확인할 수 있습니다.

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

그리고 자바 스크립트를 켜면 실제로 볼 수 있습니다. 페이지 소스 예 :

<!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>

3

ASP 버튼에서 PostbackUrl을 설정하여 다른 페이지에 게시 할 수 있습니다.

코드 숨김으로해야 할 경우 Server.Transfer를 사용해보십시오.


2

@매트,

여전히 HttpWebRequest를 사용하고 수신 한 응답을 실제 출력 스트림 응답으로 보내면 응답이 사용자에게 다시 제공됩니다. 유일한 문제는 상대 URL이 손상된다는 것입니다.

그래도 작동 할 수 있습니다.


2

내가 할 일은 다음과 같습니다.

runat = "server"속성이없는 표준 양식에 데이터를 넣고 대상 오프 사이트 페이지에 게시 할 양식의 조치를 설정하십시오. 제출하기 전에 XmlHttpRequest를 사용하여 서버에 데이터를 제출 하고 응답을 분석합니다. 응답이 오프 사이트 POSTing으로 넘어 가야한다는 것을 의미하는 경우 I (JavaScript)가 포스트를 계속 진행하고 그렇지 않으면 내 사이트의 페이지로 리디렉션합니다.


작동하지만 모든 ASP.NET 서버 측 기능을 느슨하게해야합니다.
senfo

2

PHP에서는 cURL을 사용하여 POST 데이터를 보낼 수 있습니다. .NET과 비슷한 것이 있습니까?

예, HttpWebRequest, 아래 내 게시물을 참조하십시오.


2

GET (및 HEAD) 방법을 사용하여 부작용이있는 작업을 수행해서는 안됩니다. 부작용으로 인해 웹 응용 프로그램의 상태가 업데이트되거나 신용 카드에 청구될 수 있습니다. 동작에 부작용이있는 경우 다른 방법 (POST)을 대신 사용해야합니다.

따라서 사용자 (또는 브라우저)는 GET이 수행 한 작업에 대해 책임을지지 않아야합니다. GET의 결과로 유해하거나 고가의 부작용이 발생한 경우 이는 사용자가 아닌 웹 응용 프로그램의 결함입니다. 사양에 따르면, 사용자 에이전트 GET 또는 HEAD 요청에 대한 응답이 아닌 경우 자동으로 리디렉션을 따라 가지 않아야합니다.

물론 많은 GET 요청은 로그 파일에 추가하더라도 부작용이 있습니다. 중요한 것은 사용자가 아닌 응용 프로그램이 그러한 영향에 대해 책임을 져야한다는 것입니다.

HTTP 스펙의 관련 섹션은 9.1.1 및 9.1.210.3 입니다.


1

프로그래밍 방식으로 POST를 실행하고 해당되는 경우 응답을 읽은 후 리디렉션하도록 HttpWebRequest를 작성하는 것이 좋습니다.


1

복사 할 수있는 코드 기반 Pavlo Neyman의 방법을

RedirectPost (string url, T bodyPayload) 및 GetPostData ()는 소스 페이지에서 강력한 형식의 데이터를 덤프하고 대상 데이터에서 다시 가져 오려는 사용자를위한 것입니다. 데이터는 NewtonSoft Json.NET에서 직렬화 할 수 있어야하며 물론 라이브러리를 참조해야합니다.

페이지 또는 페이지의 기본 클래스에 복사하여 붙여 넣기 만하면 응용 프로그램의 어느 곳에서나 사용할 수 있습니다.

2019 년에도 Web Forms를 사용해야하는 모든 분들께 진심으로 감사드립니다.

        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;
        }

0

일반적으로 필요한 것은이 두 요청간에 상태를 전달하는 것입니다. 실제로 JavaScript에 의존하지 않는 정말 재미있는 방법이 있습니다 (<noscript /> 생각).

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

해당 쿠키를 사용하면 /redirect.html에 대한 name = value 정보 검색 요청에 따라이 이름 / 값 쌍 문자열에 최대 4K의 데이터 (일반 쿠키 제한)까지 모든 종류의 정보를 저장할 수 있습니다. 물론 이것을 피하고 대신 상태 코드와 플래그 비트를 저장해야합니다.

이 요청을 받으면 해당 상태 코드에 대한 삭제 요청으로 응답합니다.

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

내 HTTP는 약간 녹슬 었습니다 .RFC2109 및 RFC2965를 통해 이것이 실제로 얼마나 신뢰할 수 있는지 파악했습니다. 바람직하게 쿠키를 한 번만 왕복하도록하고 싶지만 타사 쿠키 도 가능하지 않은 것 같습니다. 다른 도메인으로 이전하는 경우 문제가 될 수 있습니다. 이것은 여전히 ​​가능하지만 자신의 도메인 내에서 일을 할 때만 큼 고통스럽지 않습니다.

고급 사용자가 여러 탭을 사용하고 동일한 세션에 속하는 두 개의 요청을 인터리브하도록 관리하는 경우 동시성 문제가 발생합니다 (매우 드물지만 불가능하지는 않음). 이로 인해 애플리케이션에 불일치가 발생할 수 있습니다.

의미없는 URL과 JavaScript없이 HTTP 왕복을 수행하는 <noscript /> 방식입니다

이 코드를 개념의 전문가로 제공합니다.이 코드가 익숙하지 않은 컨텍스트에서 실행되면 어떤 부분이 무엇인지 알아낼 수 있습니다.

아이디어는 리디렉션 할 때 일부 상태로 Relocate를 호출하고 재배치 한 URL이 GetState를 호출하여 데이터를 가져 오는 것입니다 (있는 경우).

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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.