Завдання
Архітектура використання
На малюнку 1 зображено схему деякої інформаційної системи (ІС)
.
ІС складається з ядра – сукупності серверів додатків, що виконують
бізнес-логіку, і Web-інтерфейсу, розташованого на WEB сервері і
надає доступ до системи через Інтернет. У наведеній
архітектурі і буде використовуватися розглядається рішення.
Малюнок 1.
Вимоги
- Web-сервер не повинен мати прямого доступу до ІС.
- ІСдолжна визначати права користувачів на виконання того чи
іншого запиту (авторизовані і анонімні запити). - Права користувачів визначаються їх роллю в ІС.
Додаткові обмеження
Всі сервіси ІС реалізовані на базі платформи MS Windows (не нижче MS
Windows 2000).
Сервери додатків системи розташовані в межах однієї локальної
мережі.
Рішення
Архітектура рішення
Відповідно до вимог розробляємо архітектуру,
представлену на малюнку 2
Малюнок 2.
Web-сервер – Надає доступ до ІВ через Інтернет
за допомогою Web-інтерфейсу, який реалізується на технології ASP.NET.
Сервер програми для WEB – Аутентифікує користувачів,
авторизує запити користувачів, маршрутизує запити від Web-сервера
до серверів ІС. Реалізуються у вигляді. NET-додатки з можливістю
віддаленого виклику його методів.
База даних системи – Зберігає дані ІС.
Сервери додатків системи – Сукупність сервісів,
реалізують бізнес-логіку ІС.
Firewall 1,2 – Шлюзи, які захищають ІС від несанкціонованого
доступу.
Протоколи взаємодії
На малюнку 3 зображено схему взаємодії компонентів ІС та
протоколи взаємодії.
Малюнок 3
Цікавий Для нас ділянку ланцюга: Web-сервер – сервер програми для
Web. Мною обрано протокол взаємодії. NET Remoting через TCP з
бінарної сериализацией через високу ефективність цього поєднання
в порівнянні з HTTP разом з SOAP.
Ідея рішення
Ідея рішення полягає в реалізації аутентифікації на рівні канальних
приймачів (ChannelSink), що вбудовуються в інфраструктуру каналу Remoting
на стороні клієнта і сервера. Аутентифікаційні інформація передається в
заголовках запиту (TransportHeaders), результати аутентифікації
передаються в заголовках відповіді сервера. Авторизація виконується з
допомогою декларативної перевірки відповідності ролі користувача.
У разі успішної аутентифікації на сервері докладання створюється
користувальницька сесія, в якій зберігаються дані користувача.
Інша користувальницька сесія створюється на Web-сервері, причому
стандартний механізм сесій ASP.NET не використовується, тому його можна
відключити в web.config.
Сесії на сервері програми та Web-сервер різні за змістом,
так як сервер додатка може зберігати обов'язкові для кожного
користувача об'єкти, цілком можливо unmanaged (COM). Взаємозв'язок між
клієнтом, Web-сервером і сервером додатка здійснюється за
ідентифікатору сесії.
Розгортання
На малюнку 4 наведена діаграма розгортання розглянутого
рішення.
Малюнок 4
Рішення складається з трьох основних. NET-збірок, що забезпечують процеси
аутентифікації, авторизації, підтримку сесій:
SecurityBase – Складання, що містить загальні для Web-сервера і
сервера програми типи і константи.
SecurityClient – Складання, що містить типи для клієнтської частини
схеми аутентифікації і типи, що забезпечують підтримку сесій на
Web-сервері. Встановлюється на Web-сервер.
SecurityServer – Складання, що містить типи для аутентифікації і
підтримки сесій на стороні сервера додатка.
Також у приклад входить складання BusinessFacade, містить типи,
забезпечують інтерфейс з сервером додатки. На Web-сервер
встановлюється скорочена версія цієї збірки, в ній містяться тільки
сигнатури методів, без змісту.
На сервері додатка встановлюється повна версія
BusinessFacade.
На Web-сервері і сервері додатка налаштовується конфігурація
Remoting.
На Web-сервері конфігурація міститься в Web.config
<system.runtime.remoting> <application name="SHR"> <client> <Wellknown type = "RemotingExample.BusinessFacade.SomeSystem, BusinessFacade "url =" tcp: / / localhost: 8039/SHR/SomeSystem.rem "/> </client> <channels> <channel ref="tcp client"> <clientProviders> <formatter ref="binary" includeVersions="false"/> <provider type = "RemotingExample.Security.ClientChannelSinkProvider, SecurityClient"/> </clientProviders> </channel> </channels> </application> </system.runtime.remoting>
Чи не сервері додатки в ConsoleServer.exe.config:
<system.runtime.remoting> <application name="SHR"> <service> <wellknown mode="Singleton" type="RemotingExample.BusinessFacade.SomeSystem, BusinessFacade "objectUri =" SomeSystem.rem "/> </service> <channels> <channel name="ServerCnannel" ref="tcp server" port="8039"> <serverProviders> <formatter ref="binary" includeVersions="false"/> <provider type = "RemotingExample.Security.ServerChannelSinkProvider, SecurityServer"/> </serverProviders> </channel> </channels> </application> </system.runtime.remoting>
Ініціалізація конфігурації Remoting на Web-сервері відбувається в
методі:
protected void Application_Start(Object sender, EventArgs e) { string configPath = System.IO.Path.Combine(Context.Server. MapPath (Context.Request.ApplicationPath), "Web.config"); RemotingConfiguration.Configure(configPath); }
Ініціалізація на сервері програми:
RemotingConfiguration.Configure ("ConsoleServer.exe.config");
Діаграма класів
На рисунку 5 наведена діаграма використовуваних класів, у таблиці 1 –
короткий опис класів.
Малюнок 5.
Таблиця 1.
Клас | Збірка | Опис |
ServerSecurityContext | SecurityServer | Містить дані користувача на стороні сервера додатка. |
ServerChannelSinkProvider | SecurityServer | Провайдер канального приймача. Поміщає канальний приймач в ланцюжок серверних канальних приймачів. |
ServerChannelSink | SecurityServer | Серверний канальний приймач. Аутентифікує користувачів. Управляє станом сесії. |
SecurityContextContainer | SecurityBase | Контейнер для призначених для користувача сесій. |
ClientSecurityContext | SecurityClient | Містить дані користувача на стороні Web-сервера. |
ClientChannelSinkProvider | SecurityClient | Провайдер канального приймача на стороні Web-сервера. |
ClientChannelSink | SecurityClient | Канальний приймач на стороні Web- сервера. |
ChannelSinkHeaders | SecurityBase | Містить назви заголовків аутентифікації. |
ISecurityContext | SecurityBase | Інтерфейс для об'єктів, що містять стан сесії. |
Аутентифікація
На малюнку 6 зображено сценарій первинної аутентифікації користувача
в ІС.
Малюнок 6.
Користувач вводить логін і пароль в Web-формі. Оброблювач відправки
форми намагається виконати аутентифікацію:
/ / Створюємо контекст для аутентифікації. / / Мета: прив'язати до поточного потоку виконання аутентифікаційні дані, / / Щоб мати до них доступ з клієнтського канального приймача ClientSecurityContext context = new ClientSecurityContext (tbName.Text, tbPassword.Text); try { / / Звертаємося до сервера додатка userData = (new RemotingExample.BusinessFacade.SomeSystem()). GetUserData(); } catch (System.Security.SecurityException ex) { / / Аутентифікація на сервері докладання пройшла невдало this.lblMessage.Text = ex.Message; return; } / / Аутентифікація вдалася / / Створюємо і записуємо користувачеві в Cookie квиток аутентифікації. SetAuthTiket(tbName.Text, context.SessionID);
Але це тільки надводна частина айсберга, який називається
аутентифікацією. Все найцікавіше відбувається, коли починають
працювати механізми Remoting, а саме – клієнтський і серверний канальні
приймачі.
Коли ми створюємо контекст для аутентифікації, ми готуємо тим самим
поле діяльності для клієнтського канального приймача –
ClientChannelSink, який і буде виконувати всю роботу по
аутентифікації клієнта на сервері програми.
Після виклику віддаленого методу:
userData = (new RemotingExample.BusinessFacade.SomeSystem ()). GetUserData ();
управління отримує клієнтський канальний пріменік ClientChannelSink,
а саме його метод:
public void ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream) / / Витягуємо контекст запиту ClientSecurityContext context = ClientSecurityContext.Current; / / Перевіряємо, аутентифікований чи контекст switch (context.AuthState) { case AuthenticationStates.Authenticated: / / Якщо аутентифікований, то додаємо в заголовки запиту до сервера / / Додатку SID контексту requestHeaders [ChannelSinkHeaders.SID_HEADER] = context.SessionID; break; default : / / Інакше додаємо логін і пароль requestHeaders [ChannelSinkHeaders.USER_NAME_HEADER] = context.Login; requestHeaders [ChannelSinkHeaders.PASSWORD_HEADER] = сontext.Password; break; } / / Виконуємо запит на сервер додатка _nextSink.ProcessMessage (msg, requestHeaders, requestStream, out responseHeaders, out responseStream); AuthenticationStates serverAuth = AuthenticationStates.NotAuthenticated; / / Отримуємо заголовок стану аутентифікації сервера програми string serverAuthHeader = (String) responseHeaders [ChannelSinkHeaders.AUTH_STATE_HEADER]; / / Аналізуємо отриманий заголовок switch (serverAuth) { / / Контекст аутентифікований на сервері докладання case AuthenticationStates.Authenticated: if (context.AuthState != AuthenticationStates.Authenticated) { / / На Web-сервері контекст ще не аутентифікований / / Створюємо Principal об'єкт для контексту string roles = responseHeaders [ChannelSinkHeaders.ROLES_HEADER]. ToString (); string[] rolesArr = roles.Split(new char[]{","}); IIdentity identity=new GenericIdentity(ClientSecurityContext.Current.Login); IPrincipal userPrincipal = new GenericPrincipal (identity, rolesArr); / / Аутентіфіціруем контекст context.SetAuthState(AuthenticationStates.Authenticated); context.SetPrincipal(userPrincipal); / / Встановлюємо ідентифікатор сесії context.SetSessionID (responseHeaders [ChannelSinkHeaders.SID_HEADER]. ToString()); / / Створюємо сесію на Web-сервері SecurityContextContainer.GetInstance () [context.SessionID] = context; } break; }
Під час виконання запиту
_nextSink.ProcessMessage(msg, requestHeaders, requestStream, out responseHeaders, out responseStream);
управління передається на сервер додатка, де в роботу першим ділом
включається серверний канальний приймач ServerChannelSink, а саме, його
метод
ServerProcessing ProcessMessage (IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream) / / Отримуємо ідентифікатор сесії із заголовків запиту string SID = (string) requestHeaders [ChannelSinkHeaders.SID_HEADER]; ServerSecurityContext context = null; if (SID == null) / / Якщо SID відсутня, пробуємо автентифікувати запит { / / Пробуємо отримати логін і пароль з заголовків запиту string userName = (String) requestHeaders [ChannelSinkHeaders.USER_NAME_HEADER]; string password = (String) requestHeaders [ChannelSinkHeaders.PASSWORD_HEADER]; AuthenticationStates authResult = AuthenticationStates.NotAuthenticated; if ((userName != null) && (password != null)) { / / Якщо логін і пароль знайдені, виконуємо аутентифікацію string roles; authResult = Authenticate(userName,password, out roles); switch (authResult) { case AuthenticationStates.Authenticated: / / Аутентифікація пройшла успішно / / Створюємо серверний контекст для користувача context = new ServerSecurityContext(userName,roles); context.SetAuthState (AuthenticationStates.Authenticated); / / Створюємо сесію на сервері докладання SecurityContextContainer.GetInstance () [context.SessionID] = context; break; default: / / Аутентифікація не вдалася. throw new System.Security.SecurityException ("Authentication failed "); } } } / / Якщо SID існує в заголовках запиту, то авторизуючи запит / / З цього SID else { / / Воостанавліваем сесію за її кодом context = (ServerSecurityContext) SecurityContextContainer.GetInstance () [SID]; if (context == null) { throw new System.Security.SecurityException ("Authorization failed"); } else { / / Асоціюємо поточний контекст з отриманим за SID ServerSecurityContext.Current = context; } } System.Security.Principal.IPrincipal orginalPrincipal = Thread.CurrentPrincipal; if (ServerSecurityContext.Current != null) { / / Асоціюємо Principal поточного потоку з Principal об'єктом контексту Thread.CurrentPrincipal = ServerSecurityContext.Current.Principal; } sinkStack.Push(this, null); ServerProcessing processing; / / Виконуємо отриманий запит на сервері докладання processing = _nextSink.ProcessMessage (sinkStack, requestMsg, requestHeaders, requestStream ,out responseMsg, out responseHeaders, out responseStream); sinkStack.Pop(this); / / Відновлюємо Principal об'єкт для потоку Thread.CurrentPrincipal = orginalPrincipal; AuthenticationStates serverAuthState = AuthenticationStates.NotAuthenticated; if (ServerSecurityContext.Current != null) serverAuthState = context.AuthState; responseHeaders = new TransportHeaders(); switch (serverAuthState) { case AuthenticationStates.Authenticated: / / Якщо аутентифікація пройшла успішно, / / Виставляємо заголовки для відправки на Web-сервер responseHeaders [ChannelSinkHeaders.AUTH_STATE_HEADER] = AuthenticationStates.Authenticated; responseHeaders[ChannelSinkHeaders.SID_HEADER] = ServerSecurityContext.Current.SessionID; responseHeaders[ChannelSinkHeaders.ROLES_HEADER] = ServerSecurityContext.Current.Roles; break; default : responseHeaders [ChannelSinkHeaders.AUTH_STATE_HEADER] = serverAuthState; break; } / / Очищаємо поточний контекст ServerSecurityContext.Current = null; / / Повертаємо управління і результати запиту в клієнтський канальний приймач return ServerProcessing.Complete;
Тепер користувач аутентифікований і може працювати з ІС. Для цього
кожен його наступний запит повинен зазначатися на основі раніше
проведеної аутентифікації, тобто спочатку Web-сервер, а потім і сервер
додатки повинні розпізнати користувача і відновити контекст його
роботи з ІС.
Сценарій процесу наведено на рисунку 7.
Малюнок 7.
Насамперед в запиті користувача до Web-сервера шукається
спеціалізоване cookie – квиток аутентифікації (authTicket). Цей
квиток містить деяку інформацію про користувача і говорить Web-сервера
про те, що користувач вже аутентифікований. Для активізації цієї
функціональності на Web-сервері необхідно включити Forms
Authentication.
Ідентифікація користувача відбувається в методі AuthenticateRequest
Web-сервера. Цей метод викликається сервером на початку обробки кожного
запиту.
/ / Отримуємо з Cookies квиток аутентифікації string cookieName = FormsAuthentication.FormsCookieName; HttpCookie authCookie = Context.Request.Cookies[cookieName]; System.Web.Security.FormsAuthenticationTicket authTicket = null; try { authTicket = System.Web.Security.FormsAuthentication.Decrypt (authCookie.Value); } catch(Exception) { return; } if (null == authTicket) { return; } / / Отримуємо ідентифікатор сесії користувача з квитка аутентифікації string sessionID = authTicket.UserData; ClientSecurityContext securityContext = null; / / Відновлюємо сесію користувача за її кодом securityContext = (ClientSecurityContext) SecurityContextContainer.GetInstance () [sessionID]; if (securityContext != null) { ClientSecurityContext.Current = securityContext; / / Асоціюємо Principal об'єкт з поточним потоком Context.User = securityContext.User; } else { System.Web.Security.FormsAuthentication.SignOut(); Response.Redirect("logout.aspx"); }
Тепер користувач аутентифікований на стороні Web-сервера і може
виконувати програми, що реалізують логіку Web-додатки. У процесі
виконання цих програм Web-сервер може звертатися до сервера
додатки. Природно, що і там запит користувача необхідно
автентифікувати. Для цього на сервер додатка передається SID,
який витягнутий з квитка аутентифікації Web-сервером. За SID
відбувається аутентифікація та відновлюється користувальницька сесія на
сервері додатки.
Авторизація
Функціональність авторизації реалізується за допомогою атрибуту
System.Security.Permissions.PrincipalPermissionAttribute,
встановлюють перед відповідними методами фасадного об'єкта
(BusinessFacade):
[PrincipalPermissionAttribute (SecurityAction.Demand, Authenticated = true, Role = "Admin")] public void DoAdminWork (string arg) { Console.WriteLine (DateTime.Now.ToString ()+": Doing Admin work: "+ arg); }
Підтримка сесій
Здійснюється за допомогою об'єктів ServerSecurityContext,
SecurityContextContainer, ClientSecurityContext на клієнтській і
серверної сторони. Ініціалізація сесії відбувається в методах
AuthenticateRequest для Web-сервера і в ProcessMessage канального
приймача для сервера програми. Об'єкти
ISecurityContext (ServerSecurityContext, ClientSecurityContext),
містять стан сесії, зберігаються в колекції
SecurityContextContainer. Ключем до сесії є SID (ідентифікатор
сесії). При ініціалізації сесія витягується з
колекції (SecurityContextContainer) і за допомогою статичного методу
Current асоціюється з поточним потоком виконання.
public static ClientSecurityContext Current { get { ClientSecurityContext currentContext = (ClientSecurityContext) System. Runtime.Remoting.Messaging.CallContext. GetData("ClientSecurityContext"); if (currentContext != null) { currentContext.lastActivity = DateTime.Now; } return currentContext; } set { if (value != null) { value.lastActivity = DateTime.Now; } System.Runtime.Remoting.Messaging. CallContext.SetData ("ClientSecurityContext", value); } }
Після ініціалізації сесії її стан є в будь-якому місці коду.
[PrincipalPermissionAttribute (SecurityAction.Demand, Authenticated = true)] public string GetUserData() { Console.WriteLine("GetUserData " + Security.ServerSecurityContext.Current.Login); }
Головне – проставити для цього посилання на SecurityBase і
SecurityServer(SecurityClient).
Висновок
Тестове додаток WebCl (рисунок 8) демонструє можливості
описаного рішення. Цей додаток, втім, як і всі рішення,
додається до цієї статті у вигляді проекту у форматі Visual Studio. Net
2003.
Наведений приклад може бути розширений. Наприклад, результатом
аутентифікації, крім повідомлення про її успішність чи неуспішність, може
стати вимога змінити пароль.
Можна організувати перевірку – "один користувач – одна сесія".
Можна додати шифрування трафіку. Властивість Items об'єктів
IsecurityContext може служити контейнером для збереження різних
об'єктів у сесії користувача. Шляхом невеликий переробки клієнтської
Здебільшого, це рішення можна адаптувати для Windows Forms-додатків. У
Загалом, поле для діяльності велике.
Так само можна додати можливості для масштабування, винісши
контейнер сесій у зовнішній сервіс, за аналогією з ASP.NET State Service
і зробивши об'єкти сесій серіалізуємих.
Якщо у когось виникнуть питання, чи ідеї та зауваження щодо поліпшення
описаного механізму, пишіть
sun_shef@msn.com
Схожі статті:
- Пошукові форми (0)
- PHP помилки безпеки (0)
- Серверні скрипти. Введення (0)
- Про техніку інтернет-реклами (0)
- Cookies в PHP. (0)
- Flash-технологія. Недоліки (0)
- Перші кроки з ERwin 4.0 - публікація в Web і будівник шаблонів звітів (0)
Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.
Коментарів поки що немає.
Ваш отзыв
Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>