Silverlight RIA Services — как наилучшим образом обрабатывать тайм-аут сеанса аутентификации клиента?

#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, указанному в этом блоге, но еще не успел разобраться. Это должно сработать для меня.