瀏覽代碼

авторизация

Евгений Колесников 2 年之前
父節點
當前提交
539457c79f
共有 2 個文件被更改,包括 167 次插入11 次删除
  1. 166 6
      articles/cs_http.md
  2. 1 5
      readme.md

+ 166 - 6
articles/cs_http.md

@@ -2,7 +2,14 @@
 :----------------:|:----------:|:----------------:
 [Аутентификация и авторизация](./api_auth.md) | [Содержание](../readme.md#разработка-своего-api) | 
 
-# HTTP запросы в C#. Получение списка материалов выбранного продукта
+# HTTP запросы в C#. Получение списка материалов выбранного продукта. Авторизация.
+
+* [HTTP-запросы (класс **HttpClient**)](#http-запросы)
+    * [GET-запрос](#get-запрос)
+    * [Разбор JSON ответа.](#разбор-json-ответа)
+    * [DELETE (Удаление записей)](#delete-удаление-записей)
+    * [POST запросы с JSON (Добавление записей в модель в терминологии REST API)](#post-запросы-с-json-добавление-записей-в-модель-в-терминологии-rest-api)
+* [Авторизация](#авторизация)
 
 Возвращаемся к проекту на C#.
 
@@ -25,7 +32,7 @@
 var client = new HttpClient();
 ```
 
-В АПИ мы использовали "базовую" авторизацию. В C# я не нашёл встроенных библиотек для облегчения формирования заголовка для "базовой" авторизации, но реализация не сложная - нпишем сами.
+В АПИ мы использовали "базовую" авторизацию. В C# я не нашёл встроенных библиотек для облегчения формирования заголовка для "базовой" авторизации, но реализация не сложная - напишем сами.
 
 Формируем base64-кодированную строку:
 
@@ -40,7 +47,7 @@ var basic = Convert.ToBase64String(
 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basic);
 ```
 
-## GET-запрос.
+### GET-запрос.
 
 Теперь можно запрашивать данные. 
 
@@ -66,7 +73,7 @@ private Task<string> GetBody(string url){
 
 Метод *GetStringAsync* возвращает объект `Task<string>`, т.е. задачу, которая выполняется в отдельном потоке и по завершении вернёт строку (тело ответа)
 
-## GET-запрос. Разбор JSON ответа.
+### Разбор JSON ответа.
 
 Для работы с JSON нужно установить **NuGet** пакет `Newtonsoft.Json` и использовать метод десериализации объекта:
 
@@ -94,7 +101,7 @@ private async void GetMaterials()
 
 Так как метод *GetBody* возвращает задачу (**Task**), то мы ставим перед ним ключевое слово **await**, то есть ждём завершения задачи, а сам метод помечаем как асинхронный (**async**), чтобы система знала, что этот метод асинхронный.
 
-## Удаление записей
+### DELETE (Удаление записей)
 
 Для вызова http-метода `DELETE` используется метод *DeleteAsync*:
 
@@ -107,7 +114,7 @@ client.DeleteAsync(
 
 При работе с таблицей **ProductMaterial** не забываем, что у нас составной первичный ключ и нужны идентификаторы и продукта и материала.
 
-## POST запросы с JSON (Добавление записей в модель в терминологии REST API)
+### POST запросы с JSON (Добавление записей в модель в терминологии REST API)
 
 1. В проекте нарисуйте форму добавления материала в текущий продукт (в окне продукции)
 
@@ -142,3 +149,156 @@ client.DeleteAsync(
         $"http://localhost:8080/material/{currentProduct.Id}", 
         json);
     ```
+
+## Авторизация
+
+Часто в встречается задача сделать авторизацию пользователя и скрыть часть информации для неавторизованного пользователя.
+
+Скрытие сделать просто - сделайте привязку (binding) атрибута _IsVisible_ к какому-нибудь свойству (мы это уже делали для кнопки массовой смены цены продукции).
+
+Показать форму с полями ввода для `логина/пароля` вы тоже уже можете.
+
+Осталось разобраться как отправить запрос авторизации на сервер и обработать его на сервере...
+
+Если отправлять обычным POST-запросом с телом запроса в JSON-формате, то вы это тоже уже умеете. Но для авторизации часто используется старый формат запроса: `application/x-www-form-urlencoded`.
+
+Запрос с таким форматом выглядит примерно так:
+
+```
+POST /login
+Content-Type: application/x-www-form-urlencoded
+
+login=qwerty
+&password=asdf
+```
+
+т.е. параметры запроса формируются как _querystring_, но передаются в теле запроса.
+
+### Реализация конечной точки в АПИ
+
+```cs
+app.MapPost(
+    "/login", 
+    async (context) =>
+    {
+        /*
+            параметры в таком формате автоматически не парсятся
+            приходится их вручную доставать из тела запроса
+        */
+        var formData = await context.Request.ReadFormAsync();
+
+        var login = formData["login"].ToString();
+        var password = formData["password"].ToString();
+
+        /*
+            "Успешность" авторизации определяем кодом ответа
+            у меня, как обычно, логин и пароль прибиты гвоздями
+            но в реальном приложении надо читать из базы
+        */
+        if (login == "admin" && password == "password") 
+        {
+            /*
+                Тут можно передать не только код, 
+                но и информацию о пользователе
+            */
+            context.Response.StatusCode = 200;
+        }
+        else
+            context.Response.StatusCode = 401;
+    }).AllowAnonymous();
+```
+
+### Авторизация в клиентском приложении
+
+>Кнопку "Авторизоваться" и окно авторизации сделайте сами
+
+Для хранения информации об авторизации заведем класс **Globals** со статическими свойствами:
+
+```cs
+public class Globals
+{
+    public static string? token = null;
+
+    public static bool isAuth() => token != null;
+
+    public static void createToken(string login, string password)
+    {
+        token = Convert.ToBase64String(
+            ASCIIEncoding.ASCII.GetBytes($"{login}:{password}"));
+    }
+}
+```
+
+У нас остаётся базовая авторизация и я сразу формирую токен в нужном формате. Если используется авторизация по токену (**Bearer**), то метод _createToken_ не нужен, просто сохранить токен: `Globals.token = "токен, полученный при авторизации"`.
+
+И в окне авторизации реализуем отправку запроса:
+
+```cs
+private async void AuthButton_OnClick(object? sender, RoutedEventArgs e)
+{
+    /* 
+        создаем словарь
+        и записываем в него данные в виде пары ключ/значение
+        для кадого передаваемого параметра
+    */
+    var param = new Dictionary<string, string>
+    {
+        { "login", LoginTextBox.Text },
+        { "password", PasswordTextBox.Text }
+    };
+    var client = new HttpClient();
+
+    /*
+        В инициализатор запроса добавляется содержимое
+    */
+    var req = new HttpRequestMessage(
+        HttpMethod.Post, 
+        "http://localhost:8080/login")
+    {
+        Content = new FormUrlEncodedContent(param)
+    };
+
+    var response = await client.SendAsync(req);
+
+    /*
+        При успешной авторизации сохраняем токен
+    */
+    if (response.StatusCode == HttpStatusCode.OK)
+    {
+        Globals.createToken(
+            LoginTextBox.Text, 
+            PasswordTextBox.Text);
+
+        /*
+            тут закрываем окно авторизации,
+            а в главном окне обновляем состояние 
+            визуальных элементов доступных только
+            авторизованному пользователю
+        */
+    }
+    /*
+        в else можно написать логику для неуспешной авторизации,
+        например, показать окно
+    */
+
+}
+```
+
+>В коде используются свойства *LoginTextBox* и *PasswordTextBox* - это имена соответствующих визуальных элементов в окне авторизации.
+
+Ну и во всех запросах, требующих авторизации, вставляем токен авторизации:
+
+```cs
+private Task<string> GetBody(string url)
+{
+    var client = new HttpClient();
+
+    client.DefaultRequestHeaders.Authorization = 
+        new AuthenticationHeaderValue(
+            "Basic", 
+            Globals.token   // берём готовый
+        );
+
+    return client.GetStringAsync(url);
+}
+```

+ 1 - 5
readme.md

@@ -353,11 +353,7 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Авторизация и аутентификация. Методы авторизации. Basic-авторизация.](./articles/api_auth.md)
 
-1. [HTTP запросы в C#. Получение списка материалов выбранного продукта](./articles/cs_http.md)
-
-<!-- TODO 
-  окно авторизации c#
--->
+1. [HTTP запросы в C#. Получение списка материалов выбранного продукта. Авторизация.](./articles/cs_http.md)
 
 <!-- 1. [C# Параллельное программирование и асинхронность](./articles/cs_async_await.md) -->