Второй вход вызывает бесконечный цикл перенаправления после первого успешного входа MVC .NET 5 OWIN ADAL OpenIDConnect
Первый пост, так будь нежнее!:)
Я занимаюсь разработкой веб-приложения MVC .NET 5 для Office 365 и использую платформу OpenIDConnect. Я настроил OWIN (3) и ADAL(2) и приложение Azure AD. Вход в систему не выполняется пользователем, к домашнему контроллеру прикреплен атрибут [Authorize], что вызывает немедленное перенаправление входа в Azure AD. Я не использую роли ни в одном из моих атрибутов Authorize.
Проблема: я могу успешно войти в свои приложения - РАЗ! После первого входа в систему я закрываю браузер (или открываю новый браузер на другом компьютере) и снова запускаю приложение. Он перенаправляет меня на экран входа в Azure AD, на который я вхожу, и затем непрерывно перенаправляет между приложением и Azure, пока я не получу печально известные 400 заголовков для длительной выдачи. Заглянув в магазин печенья, я обнаружил, что в нем полно одноразовых номеров. Я проверяю кэш (рецепт Vittorio EFADALCache, хотя я использовал TokenCache.DefaultShared, когда была обнаружена эта проблема), и он содержит сотни строк данных кэша (только одна строка, сгенерированная с успешным входом).
Я могу видеть, как перенаправления происходят через окно вывода, что новый маркер доступа и обновления генерируется каждый цикл туда и обратно:
Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - AcquireTokenByAuthorizationCodeHandler: Resource value in the token response was used for storing tokens in the cache
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - AcquireTokenByAuthorizationCodeHandler: Resource value in the token response was used for storing tokens in the cache
Microsoft.IdentityModel.Clients.ActiveDirectory Information: 2 : 31/07/2015 12:31:52: - TokenCache: Deserialized 1 items to token cache.
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: - TokenCache: Deserialized 1 items to token cache.
Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: Storing token in the cache...
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: Storing token in the cache...
Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: An item was stored in the cache
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: An item was stored in the cache
Microsoft.IdentityModel.Clients.ActiveDirectory Information: 2 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - AcquireTokenHandlerBase: === Token Acquisition finished successfully. An access token was retuned:
Access Token Hash: PN5HoBHPlhhHIf1lxZhEWb4B4Hli69UKgcle0w7ssvo=
Refresh Token Hash: 3xmypXCO6MIMS9qUV+37uPD4kPip9WDH6Ex29GdWL88=
Expiration Time: 31/07/2015 13:31:51 +00:00
User Hash: GAWUtY8c4EKcJnsHrO6NOzwcQDMW64z5BNOvVIl1vAI=
Уведомление AuthorizationCodeReceived в моих OpenIdConnectAuthenticationOptions срабатывает при возникновении проблемы, поэтому я знаю, что Azure считает, что вход выполнен успешно (иначе перенаправление назад в приложение не произойдет):
private static void PrepO365Auth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = authority,
PostLogoutRedirectUri = "https://localhost:44300/Account/SignedOut",
RedirectUri = "https://localhost:44300/",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
ClientCredential credential = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(authority, new EFADALTokenCache(signedInUserID)); // TokenCache.DefaultShared Probably need a persistent token cache to handle app restarts etc
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
context.Code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigHelper.GraphResourceId);
return Task.FromResult(0);
},
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/ShowError?signIn=true&errorMessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
Я заменил (после обнаружения проблемы) атрибут Authorized своим собственным атрибутом Auth, унаследованным от AuthorizeAttribute, просто чтобы я мог попробовать войти в код Authorize и посмотреть, что происходит. Я создал файл PDB из версии исходного кода MVC 5 версии 5, но все, что происходит, это то, что он возвращается к моему собственному коду:(При этом я переопределил то, что мог, и нашел этот filterContext.HttpContext.User.Identity.IsAuthenticated имеет значение false, что имеет смысл, поскольку это может привести к перенаправлению обратно на вход Azure.
Итак, я знаю, что:
- Azure принимает мой логин и возвращает соответствующие токены
- При втором входе в систему, перед OnAuthorization, filterContext.HttpContext.User.Identity.IsAuthenticated возвращает false
- моя конфигурация приложения Azure в порядке, или она не будет аутентифицироваться вообще
Я думаю что:
- В настройках MVC Identity есть что-то неправильное. Azure работает правильно, иначе он не будет аутентифицироваться вообще.
- Это не проблема с cookie, поскольку проблема возникает, если вы выполняете второй вход в систему на другом компьютере.
Извините, это немного затянуто, но существует так много таких бесконечных проблем перенаправления, что мне нужно было объяснить, почему моя ситуация была другой!
То, что я ищу (если не ответ!) - это толчок в правильном направлении относительно того, как я могу отлаживать дальше.
Ценю любую помощь, которую вы можете оказать!
Энди
5 ответов
Нашли ответ для всех, кто заинтересован. Это известная ошибка в Katana, когда менеджер cookie Katana и менеджер cookie ASP .NET конфликтуют и перезаписывают файлы cookie друг друга. Полная информация и обходной путь здесь:
Показанный ниже SystemWebCookieManager теперь можно найти в пакете Nuget для https://www.nuget.org/packages/Microsoft.Owin.Host.SystemWeb/.
Добавление кода, когда CodePlex умирает:
//stick this in public void ConfigureAuth(IAppBuilder app)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
//And create this class elsewhere:
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),
});
}
}
Я тоже понял это: https://gist.github.com/irwinwilliams/823f43ef8a5e8019a95874049dbb8b00
Я столкнулся с этой проблемой и применил все исправления в Интернете. Никто из них не работал, затем я вошел и посмотрел на мое печенье. Это было огромно. Промежуточное ПО Owin усекало его, а затем атрибут [Authorize] не смог проверить личность -> отправить пользователя в oidc -> identity good - перенаправить на клиента -> truncate cookie -> не может проверить в [Authorize] -> отправить пользователя в oidc -> и т. д.
Исправление было в https://www.nuget.org/packages/Microsoft.Owin.Host.SystemWeb/ и с использованием SystemWebChunkingCookieManager.
Это разделит печенье и проанализирует их вместе.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
});
У меня не было точно описанной проблемы, но у меня был цикл перенаправления во время входа в систему на основе подключения OpenId, а также на моем компьютере DEV.
В моем случае это была простая ошибка с файлами cookie. Я получал доступ к защищенному URL через HTTP. Убедитесь, что вы получаете доступ к защищенному URL-адресу вашей проверяющей стороны через HTTPS.
После проверки подлинности cookie-файл проверки подлинности будет отправляться только по протоколу HTTPS, это означает, что при доступе к защищенному URL-адресу по HTTP браузер не будет отправлять ваш Auth-файл cookie с запросом, и, следовательно, сервер увидит, что вы не прошли проверку подлинности. В этот момент сервер перенаправит вас на сервер авторизации (где вы уже вошли). Сервер аутентификации перенаправит вас обратно на исходный URL-адрес, тем самым обеспечивая цикл перенаправления.
Это никогда не должно иметь место в ваших развертываниях, потому что вы всегда должны использовать all-SSL в своем приложении, если у вас есть такие функции, как аутентификация. Это снижает риск угона сеанса.
Приведенный ниже код решил мою проблему, добавив события сеанса в файл Golbal.asax.cs.
protected void Session_Start(object sender, EventArgs e)
{
// event is raised each time a new session is created
}
protected void Session_End(object sender, EventArgs e)
{
// event is raised when a session is abandoned or expires
}
И добавив приведенный ниже код в публичный void метод ConfigureAuth(приложение IAppBuilder) файла Startup.Auth.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
});
У меня была точно такая же проблема. Невозможно изменить URL-адрес с HTTP на HTTPS из-за других зависимостей. Окончательно разрешается путем добавления session_start и session_end в global.asax.cs
protected void Session_Start(object sender, EventArgs e)
{
// event is raised each time a new session is created
}
protected void Session_End(object sender, EventArgs e)
{
// event is raised when a session is abandoned or expires
}