Programing

ASP.NET_SessionId + OWIN 쿠키는 브라우저로 보내지 않습니다

crosscheck 2020. 6. 25. 08:16
반응형

ASP.NET_SessionId + OWIN 쿠키는 브라우저로 보내지 않습니다


Owin 쿠키 인증 사용에 이상한 문제가 있습니다.

IIS 서버 인증을 시작하면 IE / Firefox 및 Chrome에서 완벽하게 작동합니다.

인증으로 몇 가지 테스트를 시작하고 다른 플랫폼에서 로그인했으며 이상한 오류가 발생했습니다. 산발적으로 Owin 프레임 워크 / IIS는 브라우저에 쿠키를 보내지 않습니다. 코드가 올바르게 실행되는 사용자 이름과 비밀번호를 입력하지만 쿠키가 브라우저에 전혀 전달되지 않습니다. 서버를 다시 시작하면 작동하기 시작하고 어느 시점에서 로그인을 시도하고 다시 쿠키 배달을 중지합니다. 코드를 단계별로 실행하면 아무 작업도 수행되지 않으며 오류가 발생하지 않습니다.

 app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationMode = AuthenticationMode.Active,
            CookieHttpOnly = true,
            AuthenticationType = "ABC",
            LoginPath = new PathString("/Account/Login"),
            CookiePath = "/",
            CookieName = "ABC",
            Provider = new CookieAuthenticationProvider
               {
                  OnApplyRedirect = ctx =>
                  {
                     if (!IsAjaxRequest(ctx.Request))
                     {
                        ctx.Response.Redirect(ctx.RedirectUri);
                     }
                 }
               }
        });

그리고 내 로그인 절차에는 다음 코드가 있습니다.

IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
                            authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

var authentication = HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("ABC");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.User_ID.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Role, role.myRole.ToString()));
    authentication.AuthenticationResponseGrant =
        new AuthenticationResponseGrant(identity, new AuthenticationProperties()
                                                   {
                                                       IsPersistent = isPersistent
                                                   });

authenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, identity);

업데이트 1 : 문제의 한 가지 원인은 문제가 시작되는 세션에 항목을 추가 할 때입니다. 간단한 것을 추가 Session.Content["ABC"]= 123하면 문제가 발생하는 것 같습니다.

내가 만들 수있는 것은 다음과 같습니다 : 1) (Chrome) 로그인하면 ASP.NET_SessionId + 내 인증 쿠키가 나타납니다. 2) session.contents를 설정하는 페이지로 이동합니다 ... 3) 새 브라우저 (Firefox)를 열고 로그인을 시도하면 ASP.NET_SessionId를받지 않거나 인증 쿠키를 얻지 않습니다 .4) 첫 번째 브라우저 ASP.NET_SessionId가 있으며 계속 작동합니다. 이 쿠키를 제거하는 순간 IP 주소 (10.xxx) 및 localhost에서 작업중 인 다른 모든 브라우저와 동일한 문제가 있습니다.

업데이트 2 :ASPNET_SessionId OWIN으로 인증하기 전에 login_load 페이지 에서 처음으로 강제 생성 합니다.

1) OWIN으로 인증하기 전에 Session.Content로그인 페이지에서 임의의 값을 만들어 ASP.NET_SessionId를 시작합니다 .2) 인증하고 추가 세션을 만듭니다 .3) 다른 브라우저가 작동하는 것 같습니다.

이것은 기괴하다. 나는 이것이 ASP 및 OWIN과 관련이 있다고 생각할 수 있습니다.

업데이트 3- 둘 사이의 이상한 동작.

추가 이상한 동작 식별-Owin 시간 초과 및 ASP 세션이 다릅니다. 내가보고있는 것은 내 Owin 세션이 일부 메커니즘을 통해 ASP 세션보다 오래 살아남는 것입니다. 그래서 로그인 할 때 : 1.) 쿠키 기반 인증 세션이 있습니다 .2) 몇 가지 세션 변수를 설정했습니다.

owin 쿠키 세션 변수 이전의 세션 변수 (2) "die"는 강제로 다시 로그인하여 전체 응용 프로그램에서 예기치 않은 동작이 발생합니다. (사람은 로그인했지만 실제로는 로그인하지 않았습니다)

3B 업데이트

약간의 파기 후 페이지에서 "양식"인증 시간 초과 및 세션 시간 초과가 일치해야한다는 의견이있었습니다. 나는 정상적으로 두 가지가 동기화되어 있다고 생각하지만 어떤 이유로 든 두 가지가 동기화되어 있지 않습니다.

대안 요약

1) 인증 전에 항상 세션을 먼저 생성하십시오. 응용 프로그램을 시작할 때 기본적으로 세션 만들기Session["Workaround"] = 0;

2) [실험] 쿠키를 유지하는 경우 OWIN 시간 초과 / 길이가 web.config의 sessionTimeout보다 길어야합니다 (테스트 중).


동일한 문제가 발생하여 OWIN ASP.NET 호스팅 구현의 원인을 추적했습니다. 나는 그것이 버그라고 말할 것입니다.

일부 배경

내 연구 결과는 다음 어셈블리 버전을 기반으로합니다.

  • Microsoft.Owin, 버전 = 2.0.2.0, Culture = neutral, PublicKeyToken = 31bf3856ad364e35
  • Microsoft.Owin.Host.SystemWeb, 버전 = 2.0.2.0, Culture = neutral, PublicKeyToken = 31bf3856ad364e35
  • System.Web, 버전 = 4.0.0.0, 문화 = 중립, PublicKeyToken = b03f5f7f11d50a3a

OWIN은 자체 추상화를 사용하여 응답 쿠키 ( Microsoft.Owin.ResponseCookieCollection )에 대해 작업 합니다. 이 구현은 응답 헤더 컬렉션을 직접 래핑하고 이에 따라 Set-Cookie 헤더를 업데이트합니다 . OWIN ASP.NET 호스트 ( Microsoft.Owin.Host.SystemWeb )는 System.Web.HttpResponse를 감싸고 헤더 컬렉션입니다. 따라서 OWIN을 통해 새로운 쿠키가 생성되면 응답 Set-Cookie 헤더가 직접 변경됩니다.

그러나 ASP.NET은 자체 추상화를 사용하여 응답 쿠키를 처리합니다. 이것은 System.Web.HttpResponse.Cookies 속성 으로 노출 되며 봉인 클래스 System.Web.HttpCookieCollection에 의해 구현됩니다 . 이 구현은 응답 Set-Cookie 헤더를 직접 래핑하지 않지만 일부 최적화 및 소수의 내부 알림을 사용하여 상태가 응답 객체로 변경되었음을 나타냅니다.

그런 다음 HttpCookieCollection 변경 상태가 테스트되고 ( System.Web.HttpResponse.GenerateResponseHeadersForCookies () ) 요청 수명이 늦어 쿠키가 Set-Cookie 헤더 로 직렬화 되는 시점 있습니다. 이 컬렉션이 특정 상태 인 경우 전체 Set-Cookie 헤더가 먼저 삭제되고 컬렉션에 저장된 쿠키에서 다시 만들어집니다.

ASP.NET 세션 구현은 System.Web.HttpResponse.Cookies 속성을 사용하여 ASP.NET_SessionId 쿠키를 저장합니다. 또한 ASP.NET 세션 상태 모듈 ( System.Web.SessionState.SessionStateModule )에는 기본적으로 s_sessionEverSet이라는 정적 속성을 통해 구현되는 몇 가지 기본 최적화가 있습니다. 애플리케이션에 세션 상태로 무언가를 저장 한 경우이 모듈은 각 요청에 대해 약간 더 많은 작업을 수행합니다.


로그인 문제로 돌아 가기

이 모든 부분을 통해 시나리오를 설명 할 수 있습니다.

사례 1-세션이 설정되지 않았습니다

System.Web.SessionState.SessionStateModule , s_sessionEverSet 특성이 false입니다. 세션 상태 모듈에서 세션 ID를 생성 하지 않으며 System.Web.HttpResponse.Cookies 수집 상태가 변경된 것으로 감지되지 않습니다 . 이 경우 OWIN 쿠키가 브라우저로 올바르게 전송되고 로그인이 작동합니다.

사례 2-세션이 응용 프로그램 어딘가에서 사용되었지만 사용자가 인증을 시도하기 전에는 사용되지 않았습니다.

System.Web.SessionState.SessionStateModule , s_sessionEverSet 특성이 true입니다. 세션 ID는 SessionStateModule의해 생성되고 ASP.NET_SessionId는 System.Web.HttpResponse.Cookies 컬렉션에 추가 되지만 사용자 세션이 실제로 비어 있으면 요청 수명이 나중에 제거됩니다. 이 경우에는 System.Web.HttpResponse.Cookies의 수집 상태 변화로서 검출 하고 설정 쿠키 쿠키 헤더 값 직렬화 헤더 전에 먼저 삭제된다.

이 경우 OWIN 응답 쿠키는 "손실"되고 사용자는 인증되지 않으며 로그인 페이지로 다시 리디렉션됩니다.

사례 3-사용자가 인증을 시도하기 전에 세션이 사용됩니다

System.Web.SessionState.SessionStateModule , s_sessionEverSet 특성이 true입니다. 세션 ID는 SessionStateModule의해 생성되고 ASP.NET_SessionId는 System.Web.HttpResponse.Cookies에 추가됩니다 . System.Web.HttpCookieCollectionSystem.Web.HttpResponse.GenerateResponseHeadersForCookies ()의 내부 최적화로 인해 Set-Cookie 헤더가 먼저 지워지지 않고 업데이트 만됩니다.

이 경우 OWIN 인증 쿠키와 ASP.NET_SessionId 쿠키가 모두 응답으로 전송되고 로그인이 작동합니다.


쿠키와 관련된 일반적인 문제

보다시피 문제는보다 일반적이며 ASP.NET 세션에만 국한되지 않습니다. Microsoft.Owin.Host.SystemWeb을 통해 OWIN을 호스팅 하고 있고 사용자가 무언가 System.Web.HttpResponse.Cookies 컬렉션을 직접 사용하는 경우 위험에 노출됩니다.

예를 들어이 작동 하고 두 쿠키가 모두 브라우저로 올바르게 전송됩니다 ...

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";

    return View();
}

그러나 이것은 그렇지 않으며 OwinCookie는 "잃어버린"것입니다 ...

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
    HttpContext.Response.Cookies.Remove("ASPCookie");

    return View();
}

둘 다 VS2013, IISExpress 및 기본 MVC 프로젝트 템플릿에서 테스트되었습니다.


@TomasDolezal의 훌륭한 분석으로 시작하여 Owin과 System.Web 소스를 모두 살펴 보았습니다.

문제는 System.Web에 쿠키 정보의 자체 마스터 소스가 있으며 Set-Cookie 헤더가 아니라는 것입니다. Owin은 Set-Cookie 헤더에 대해서만 알고 있습니다. 해결 방법은 Owin에서 설정 한 쿠키도 모음에 설정되어 있는지 확인하는 것 HttpContext.Current.Response.Cookies입니다.

쿠키 미들웨어 등록 바로 위에 배치하기 위해 정확하게 수행 하는 작은 미들웨어 ( source , nuget )를 만들었습니다 .

app.UseKentorOwinCookieSaver();

app.UseCookieAuthentication(new CookieAuthenticationOptions());

간단히 말해 .NET 쿠키 관리자는 OWIN 쿠키 관리자를 극복하고 OWIN 레이어에 설정된 쿠키를 덮어 씁니다 . 해결 방법은 Katana Project here의 솔루션으로 제공되는 SystemWebCookieManager 클래스 를 사용하는 입니다. 이 클래스 또는 이와 유사한 클래스 를 사용해야합니다. OWIN이 .NET 쿠키 관리자를 사용하도록하여 불일치가 없도록합니다 .

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}

응용 프로그램 시작시 OWIN 종속성을 만들 때 지정하십시오.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    ...
    CookieManager = new SystemWebCookieManager()
    ...
});

비슷한 답변이 여기에 제공되었지만 문제를 해결하는 데 필요한 모든 코드베이스가 포함되어 있지 않으므로 Katana Project에 대한 외부 링크가 중단 될 수 있으므로 여기에 추가해야합니다. 여기에 해결책으로.


Katana 팀은 Tomas Dolezar가 제기 문제 에 답변하고 해결 방법에 대한 문서를 게시했습니다 .

해결 방법은 두 가지 범주로 나뉩니다. 하나는 System.Web을 재구성하여 Response.Cookies 컬렉션을 사용하지 않고 OWIN 쿠키를 덮어 쓰지 않도록하는 것입니다. 다른 방법은 영향을받는 OWIN 구성 요소를 재구성하여 쿠키를 System.Web의 Response.Cookies 컬렉션에 직접 쓰도록하는 것입니다.

  • 인증 전에 세션이 설정되어 있는지 확인 : System.Web과 Katana 쿠키 간의 충돌은 요청마다 발생하므로 응용 프로그램이 인증 흐름 전에 일부 요청에 대해 세션을 설정할 수 있습니다. 사용자가 처음 도착하면 쉽게 수행 할 수 있지만 세션 또는 인증 쿠키가 만료되거나 새로 고쳐야 할 경우 나중에 보장하기가 더 어려울 수 있습니다.
  • SessionStateModule 비활성화-응용 프로그램이 세션 정보에 의존하지 않지만 세션 모듈이 여전히 위의 충돌을 일으키는 쿠키를 설정하는 경우 세션 상태 모듈을 비활성화하는 것을 고려할 수 있습니다.
  • System.Web의 쿠키 모음에 직접 쓰도록 CookieAuthenticationMiddleware를 다시 구성하십시오.
app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

문서에서 SystemWebCookieManager 구현을 참조하십시오 (위 링크).

더 자세한 정보는 여기

편집하다

문제를 해결하기 위해 취한 단계 아래 1과 2 모두 문제를 별도로 해결했지만 다음과 같은 경우에 모두 적용하기로 결정했습니다.

1. SystemWebCookieManager 사용

2. 세션 변수를 설정하십시오 :

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);

    // See http://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser/
    requestContext.HttpContext.Session["FixEternalRedirectLoop"] = 1;
}

(참고 : 위의 Initialize 메서드는 base.Initialize가 세션을 사용할 수있게하므로 수정의 논리적 인 장소입니다. 그러나 OpenId에는 먼저 익명 요청이 있기 때문에 나중에 수정을 적용한 다음 다시 OpenId 공급자로 리디렉션 할 수 있습니다. 수정 프로그램이 첫 번째 익명 요청 중에 세션 변수를 이미 설정 한 상태에서 다시 리디렉션이 발생하기 전에 문제를 해결하는 동안 앱으로 다시 리디렉션 한 후 문제가 발생 함)

편집 2

Katana 프로젝트 에서 복사하여 붙여 넣기 2016-05-14 :

이거 추가 해봐:

app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

...이:

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}

OWIN 미들웨어에서 쿠키를 직접 설정하는 경우 사용 OnSendingHeaders하면 문제가 해결되는 것 같습니다.

예를 들어, 아래 코드를 사용 하지 owinResponseCookie2않아도 설정됩니다 owinResponseCookie1.

private void SetCookies()
{
    var owinContext = HttpContext.GetOwinContext();
    var owinResponse = owinContext.Response;

    owinResponse.Cookies.Append("owinResponseCookie1", "value1");

    owinResponse.OnSendingHeaders(state =>
    {
        owinResponse.Cookies.Append("owinResponseCookie2", "value2");
    },
    null);

    var httpResponse = HttpContext.Response;
    httpResponse.Cookies.Remove("httpResponseCookie1");
}

Answers have been provided already, but in owin 3.1.0, there is a SystemWebChunkingCookieManager class that can be used.

https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs

https://raw.githubusercontent.com/aspnet/AspNetKatana/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    ...
    CookieManager = new SystemWebChunkingCookieManager()
    ...
});

The fastest one-line code solution:

HttpContext.Current.Session["RunSession"] = "1";

Just add this line before CreateIdentity method:

HttpContext.Current.Session["RunSession"] = "1";
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
_authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberLogin }, userIdentity);

I had the same symptom of the Set-Cookie header not being sent but none of these answers helped me. Everything worked on my local machine but when deployed to production the set-cookie headers would never get set.

It turns out it was a combination of using a custom CookieAuthenticationMiddleware with WebApi along with WebApi compression support

Luckily I was using ELMAH in my project which let me to this exception being logged:

System.Web.HttpException Server cannot append header after HTTP headers have been sent.

Which led me to this GitHub Issue

Basically, if you have an odd setup like mine you will want to disable compression for your WebApi controllers/methods that set cookies, or try the OwinServerCompressionHandler.

참고URL : https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser

반응형