#silverlight #silverlight-4.0 #asp.net-membership #wcf-ria-services
#Silverlight #Silverlight-4.0 #asp.net-членство #wcf-ria-services
Вопрос:
Я создал приложение с Silverlight4, RIA Services, и я использую ASP.NET Членство для аутентификации / авторизации.
В моем web.config есть это:
<system.web>
<sessionState timeout="20"/>
<authentication mode="Forms">
<forms name="_ASPXAUTH" timeout="20"/>
</authentication>
Я прочитал несколько различных стратегий о том, как справиться с таймаутом аутентификации / сеанса на стороне клиента. То есть: если клиент простаивает в течение x минут (здесь 20), а затем он что-то делает с пользовательским интерфейсом, который запускает вызов RIA / WCF, я хочу перехватить это событие и обработать его соответствующим образом (например, вернуть их на экран входа в систему) — в двух словах: мне нужно способ отличить добросовестное исключение DomainException на стороне сервера от сбоя авторизации из-за истечения времени ожидания сеанса.
AFAIK: не существует типизированного исключения или свойства, которые могли бы это определить. Единственный способ, которым я смог определить это — что кажется взломом: проверить строку сообщения об ошибке и поискать что-то вроде «Доступ запрещен» или «отказано». Например: что-то вроде этого:
if (ex.Message.Contains("denied"))
// this is probably an auth failure b/c of a session timeout
Итак, это то, что я сейчас делаю, и это работает, если я запускаю и отлаживаю либо с помощью встроенного сервера из VS2010, либо при запуске в localhost IIS. Если я установлю тайм-аут на 1 минуту, войду в систему, подожду более минуты и вызову другой вызов, я установлю точку останова для исключения и введу приведенный выше блок кода if, и все будет хорошо.
Затем я развертываю приложение на удаленном сервере IIS7 и пробую тот же тест, но он не работает. Итак, я добавил трассировку журнала, и вот событие, в котором произошло исключение:
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
<System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
<EventID>131076</EventID>
<Type>3</Type>
<SubType Name="Error">0</SubType>
<Level>2</Level>
<TimeCreated SystemTime="2011-10-30T22:13:54.6425781Z" />
<Source Name="System.ServiceModel" />
<Correlation ActivityID="{20c26991-372f-430f-913b-1b72a261863d}" />
<Execution ProcessName="w3wp" ProcessID="4316" ThreadID="24" />
<Channel />
<Computer>TESTPROD-HOST</Computer>
</System>
<ApplicationData>
<TraceData>
<DataItem>
<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Error">
<TraceIdentifier>http://msdn.microsoft.com/en-US/library/System.ServiceModel.Diagnostics.TraceHandledException.aspx</TraceIdentifier>
<Description>Handling an exception.</Description>
<AppDomain>/LM/W3SVC/1/ROOT/sla-2-129644844652558594</AppDomain>
<Exception>
<ExceptionType>System.ServiceModel.FaultException`1[[System.ServiceModel.DomainServices.Hosting.DomainServiceFault, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message></Message>
<StackTrace>
at System.ServiceModel.DomainServices.Hosting.QueryOperationBehavior`1.QueryOperationInvoker.InvokeCore(Object instance, Object[] inputs, Object[]amp;amp; outputs)
at System.ServiceModel.DomainServices.Hosting.DomainOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]amp;amp; outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpcamp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpcamp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpcamp;amp; rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
</StackTrace>
<ExceptionString>System.ServiceModel.FaultException`1[System.ServiceModel.DomainServices.Hosting.DomainServiceFault]: (Fault Detail is equal to System.ServiceModel.DomainServices.Hosting.DomainServiceFault).</ExceptionString>
</Exception>
</TraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>
Проблема в том, что у меня нет строки в сообщении об ошибке, которая указывает «отказано» или «Доступ запрещен», и я не уверен, почему это решение работает на локальном хосте IIS или хосте VS2010, но не на удаленном сервере IIS7. Есть ли какие-то неясные настройки конфигурации, которые мне здесь не хватает? Есть ли лучший способ сделать это вообще?
Ответ №1:
Вы, вероятно, уже справились с этим, но в этой статье описывается использование исключения DomainOperationException и проверка кодов ошибок.
dex.ErrorCode == ErrorCodes.NotAuthenticated || dex.ErrorCode == ErrorCodes.Unauthorized
Для удобного доступа (и на случай, если мы потеряем доступ к блогу) вот статья в блоге Джоша Истберна:
Вопрос, который часто возникает у разработчиков, работающих с Silverlight и WCF RIA Services: почему мое приложение Silverlight выдает исключение, когда оно простаивало в течение определенного периода времени? Как и следовало ожидать, это связано с истечением времени ожидания сеанса аутентификации. Но это не так просто. Поскольку Silverlight использует архитектуру клиент / сервер, клиент может работать независимо от сервера в течение неопределенного периода времени. Тайм-аут на стороне сервера реализуется только тогда, когда клиент Silverlight выполняет вызов серверу. Существует несколько вариантов решения проблемы тайм-аута клиент-сервер (и вы можете придумать еще несколько): Если вас не беспокоят последствия для безопасности удаления тайм-аута сеанса, вы можете либо увеличить значение тайм-аута в web.config, либо создать DispatcherTimer вклиент Silverlight, который вызывает простой метод на сервере, чтобы действовать как «Сохранить жизнь». Добавьте к клиенту Silverlight параметр DispatcherTimer, который синхронизируется с таймаутом на стороне сервера, и предупредите / предложите пользователю сохранить сеанс активным до истечения времени или повторно аутентифицировать его, если оно уже истекло. Однако это требует дополнительных усилий для синхронизации таймеров при выполнении новых запросов к серверу. Разрешите серверу обрабатывать тайм-аут, как обычно, и корректно обрабатывать тайм-аут на клиенте Silverlight. Это означает, что тайм-аут определяется активностью вызова сервера, А НЕ активностью, ограниченной клиентом Silverlight (т. Е. Доступом к данным на стороне клиента в контексте). Из этих трех вариантов я нахожу третий наилучшим балансом безопасности и удобства использования, в то же время не добавляя ненужной сложности приложению. Чтобы обрабатывать эти тайм-ауты на стороне сервера глобально, вы можете добавить следующую логику либо в метод Application_UnhandledException в App.xaml.cs, либо в вашу глобальную конструкцию загрузки ViewModel, если она у вас есть:
// Check for Server-Side Session Timeout Exception
var dex = e.ExceptionObject as DomainOperationException;
if ((dex != null) amp;amp; (dex.ErrorCode == ErrorCodes.NotAuthenticated || dex.ErrorCode == ErrorCodes.Unauthorized) amp;amp; WebContext.Current.User.IsAuthenticated)
{
// A server-side timeout has occurred. Call LoadUser which will automatically
// authenticate if "Remember Me" was checked, or prompt for the user to log on again
WebContext.Current.Authentication.LoadUser(Application_UserLoaded, null);
e.Handled = true;
}
В классе errorCodes определены следующие константы:
public static class ErrorCodes
{
public const int NotAuthenticated = 0xA01;
public const int Unauthorized = 401;
}
Когда время ожидания сеанса на стороне сервера истекает, все последующие вызовы возвращают исключение DomainOperationException. Проверяя возвращенный код ошибки, вы можете определить, является ли это ошибкой аутентификации, и обработать ее соответствующим образом. В моем примере я вызываю WebContext.Current.Аутентификация.loadUser(), который попытается повторно аутентифицировать пользователя, если это возможно. Даже если пользователь не может быть автоматически повторно аутентифицирован, он обратится к моему методу Application_UserLoaded. Там я могу проверить WebContext.Current.User.Аутентифицируется, чтобы определить, следует ли продолжить предыдущую операцию или мне нужно перенаправить обратно на домашнюю страницу и повторно ввести запрос для входа. Вот пример некоторого кода в обратном вызове Appliation_UserLoaded, который показывает диалоговое окно входа в систему, если пользователь не прошел проверку подлинности:
// Determine if the user is authenticated
if (!WebContext.Current.User.IsAuthenticated)
{
// Show login dialog automatically
LoginRegistrationWindow loginWindow = new LoginRegistrationWindow();
loginWindow.Show();
}
Чтобы протестировать свой код, вы можете установить значение тайм-аута в web.config на
небольшое значение, чтобы тайм-ауты происходили быстро:
<authentication mode="Forms">
<forms name=".Falafel_ASPXAUTH" timeout="1" />
</authentication>
Если вы хотите увидеть весь этот код в рабочем решении, ознакомьтесь с нашим шаблоном Silverlight RIA на CodePlex.
Комментарии:
1. Спасибо — это именно то, что я искал. Я направлялся к варианту 3, указанному в этом блоге, но еще не успел разобраться. Это должно сработать для меня.