Аутентификация через ЕСИА OAuth2

от автора

В этой статье я напишу как я прикручивал ЕСИА к сайту, возможны и другие сценарии.

Первым делом надо создать сертификат для генерации подписи в формате pfx с закрытым ключем.
Для этого я использовал «PFX Certificate Generator». В настройках указывается служебная информация и информация для хеширования: sha256, длина ключа 2048.

После этого надо установить сертификат. Добавляем оснастку для сертификатов. открываем ветку «локальное хранилище», потом импортируем туда наш сгенеренный сертификат. Потом копируем его в ветку «Доверенные корневые».
После этого надо поставить к нему разрешение. В ветке «Личные» находим наш импортированный сертификат кликаем правой кнопкой мыши выбираем «Все задачи» потом «Управление закрытыми ключами». Далее добавляем разрешения на группу «Все».

После этого надо экспортировать наш сертификат в формате cer и передать в ЕСИА, там его зарегистрируют.

Теперь приступим к написанию кода. У себя я использовал NancyFx. Итак…

        static string client_id = "123456"; //Мнемоника системы, его можно узнать у ЕСИА         static string state = Guid.NewGuid().ToString("D"); //гуид для всяких проверок         static string server_url = "https://esia.gosuslugi.ru/aas/oauth2/ac"; //адресс по которому есиа вернет авторизационный код         static string server_url_2 = "https://esia.gosuslugi.ru/aas/oauth2/te"; //адресс по которому получим маркер         static string server_url_prns = "https://esia.gosuslugi.ru/rs/prns/"; //тут мы узнаем ФИО пользователя который к нам хочет залогиниться  //тестовые настройки        /* static string server_url = "https://esia-portal1.test.gosuslugi.ru/aas/oauth2/ac";         static string server_url_2 = "https://esia-portal1.test.gosuslugi.ru/aas/oauth2/te";         static string server_url_prns = "https://esia-portal1.test.gosuslugi.ru/rs/prns/";*/         public EsiaModule()         {             Get["/ESIA"] = _ =>             {                  //string scope = "openid";                 string scope = "fullname";                 string timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");                 string access_type = "online";                 string response_type = "code";                 string redirect_uri = Request.Url.Scheme + "://" + Request.Url.HostName + (Request.Url.Port == 80 ? "" : (":" + Request.Url.Port.ToString())) + "/ESIA-OK";  //адресс редиректа, после того как пользователь ввел данные в есиа                 string client_secret = "";                 //Генерим подпись с помощью нашего сертификата                 string msg = scope + timestamp + client_id + state;                 byte[] msgBytes = Encoding.UTF8.GetBytes(msg);                 var signerCert = DetachedSignature.GetSignerCert();                 byte[] encodedSignature = DetachedSignature.SignMsg(msgBytes, signerCert);                 client_secret = HttpServerUtility.UrlTokenEncode(encodedSignature);                //генерим строку с параметрами                 RequestBuilder builder = new RequestBuilder();                 builder.AddParam("client_id", client_id);                 builder.AddParam("client_secret", client_secret);                 builder.AddParam("redirect_uri", redirect_uri);                 builder.AddParam("scope", scope);                 builder.AddParam("response_type", response_type);                 builder.AddParam("state", state);                 builder.AddParam("timestamp", timestamp);                 builder.AddParam("access_type", access_type);                  string red_url = server_url + "?" + builder.ToString().Replace("+", "%2b");                 return Response.AsRedirect(red_url);             };             Get["/ESIA-OK"] = _ =>             {                 //сюда нас редиректит есиа, мы проверяем state который был послан и который пришел, потом используем code                 string state_r = Request.Query["state"];                 string code = Request.Query["code"];                  if (state == state_r)                 {                     //string scope = "openid";                     string scope = "fullname";                     string timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");                     string redirect_uri = Request.Url.Scheme + "://" + Request.Url.HostName + (Request.Url.Port == 80 ? "" : (":" + Request.Url.Port.ToString())) + "/ESIA-OK";                     string client_secret = "";                     string msg = scope + timestamp + client_id + state;                     byte[] msgBytes = Encoding.UTF8.GetBytes(msg);                     var signerCert = DetachedSignature.GetSignerCert();                     byte[] encodedSignature = DetachedSignature.SignMsg(msgBytes, signerCert);                     client_secret = HttpServerUtility.UrlTokenEncode(encodedSignature);                      string result;                     //генерим post запрос для получения маркера                     {                         RequestBuilder builder = new RequestBuilder();                         builder.AddParam("client_id", client_id);                         builder.AddParam("code", code);                         builder.AddParam("grant_type", "authorization_code");                         builder.AddParam("client_secret", client_secret);                         builder.AddParam("state", state);                         builder.AddParam("redirect_uri", redirect_uri);                         builder.AddParam("scope", scope);                         builder.AddParam("timestamp", timestamp);                         builder.AddParam("token_type", "Bearer");                          var httpWebRequest = (HttpWebRequest)WebRequest.Create(server_url_2);                         httpWebRequest.ContentType = "application/x-www-form-urlencoded";                         httpWebRequest.Method = "POST";                         httpWebRequest.Timeout = int.MaxValue;                              WebProxy p = new WebProxy();                         p.Address = new Uri("http://192.168.2.5:8080");                         p.Credentials = new NetworkCredential(@"vrn\ituprtrans", "2015Avtobus");                         httpWebRequest.Proxy = p;                         using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))                         {                             streamWriter.Write(builder.ToString().Replace("+", "%2b"));                             streamWriter.Flush();                             streamWriter.Close();                         }                          var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();                         using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))                         {                             result = streamReader.ReadToEnd();                         }                     }                     ESIA_Marker_Answer marker = JsonConvert.DeserializeObject<ESIA_Marker_Answer>(result);//наш маркер                      string[] marker_parts = marker.access_token.Split('.');                      string header = Encoding.UTF8.GetString(base64urldecode(marker_parts[0]));                     string payload = Encoding.UTF8.GetString(base64urldecode(marker_parts[1]));                     string oid = (JsonConvert.DeserializeObject<dynamic>(payload))["urn:esia:sbj_id"];                      //генерим запрос для получения иформации о пользователе                     string user_info = "";                     {                         var httpWebRequest = (HttpWebRequest)WebRequest.Create(server_url_prns + oid);                         httpWebRequest.ContentType = "application/x-www-form-urlencoded";                         httpWebRequest.Method = "GET";                         httpWebRequest.Headers["Authorization"] = "Bearer " + marker.access_token;                         httpWebRequest.Timeout = int.MaxValue;                         WebProxy p = new WebProxy();                         p.Address = new Uri("http://127.0.0.1:8080"); //если есть прокси                         p.Credentials = new NetworkCredential(@"vrn\ituprtrans", "2015Avtobus");                         httpWebRequest.Proxy = p;                          var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();                         using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))                         {                             user_info = streamReader.ReadToEnd();                         }                     }                      string firstName = JsonConvert.DeserializeObject<dynamic>(user_info)["firstName"];                     string lastName = "";                     try                     {                         lastName = JsonConvert.DeserializeObject<dynamic>(user_info)["lastName"];                     }                     catch { }                     string middleName = "";                     try                     {                         middleName = JsonConvert.DeserializeObject<dynamic>(user_info)["middleName"];                     }                     catch { }                       // Записываю в бд данные если он не зареган и проверяю если зареган                      MyEntities db = new MyEntities();                     Helper h = new Helper();                     users u;                     if (db.users.Any(a => a.esia_oid.Trim() == oid.Trim()))                     {                         u = db.users.FirstOrDefault(a => a.esia_oid.Trim() == oid.Trim());                     }                     else                     {                         //create user                                                 u = new users                         {                             mail = oid,                             name = firstName,                             pass = h.HashWithSalt(oid),                             patronymic = middleName ?? "",                             surname = lastName ?? "",                             token = Guid.NewGuid().ToString("N"),                             role_id = 2,                             esia_oid = oid                         };                         db.users.Add(u);                         db.SaveChanges();                     }                     //добавляю куки и отправляю на главную                     DateTime expires = DateTime.UtcNow.AddYears(20);                     List<NancyCookie> cs = new List<NancyCookie> { new NancyCookie("id", u.id.ToString(), false) { Expires = expires }, new NancyCookie("token", u.token, false) { Expires = expires } };                     var resp = Response.AsRedirect("/");                     resp.AddCookie(cs[0]);                     resp.AddCookie(cs[1]);                      return resp;                 }                 else                 {                     return Response.AsJson(new { err = 1 });                 }             };         }         static byte[] base64urldecode(string arg)         {             string s = arg;             s = s.Replace('-', '+'); // 62nd char of encoding             s = s.Replace('_', '/'); // 63rd char of encoding             switch (s.Length % 4) // Pad with trailing '='s             {                 case 0: break; // No pad chars in this case                 case 2: s += "=="; break; // Two pad chars                 case 3: s += "="; break; // One pad char                 default: throw new System.Exception(                   "Illegal base64url string!");             }             return Convert.FromBase64String(s); // Standard base64 decoder         }     }      public class RequestBuilder     {         List<RequesItemClass> items = new List<RequesItemClass>();         public void AddParam(string name, string value)         {             items.Add(new RequesItemClass { name = name, value = value });         }         override public string ToString()         {             return string.Join("&", items.Select(a => a.name + "=" + a.value));         }     }      class DetachedSignature     {              static public X509Certificate2 GetSignerCert()         {                         X509Store storeMy = new X509Store(StoreName.My, StoreLocation.LocalMachine);             storeMy.Open(OpenFlags.ReadOnly);             X509Certificate2Collection certColl = storeMy.Certificates.Find(X509FindType.FindBySubjectKeyIdentifier, "12 34 56 78 90 00 00 00 11 11 11 11 11 11 11 11 11 22 33 44", false);             storeMy.Close();             return certColl[0];         }                 static public byte[] SignMsg(Byte[] msg, X509Certificate2 signerCert)         {             ContentInfo contentInfo = new ContentInfo(msg);             SignedCms signedCms = new SignedCms(contentInfo, true);             CmsSigner cmsSigner = new CmsSigner(signerCert);             signedCms.ComputeSignature(cmsSigner);             return signedCms.Encode();         }          static public bool VerifyMsg(Byte[] msg, byte[] encodedSignature)         {             ContentInfo contentInfo = new ContentInfo(msg);             SignedCms signedCms = new SignedCms(contentInfo, true);             signedCms.Decode(encodedSignature);              try             {                 signedCms.CheckSignature(true);                           }             catch (System.Security.Cryptography.CryptographicException e)             {                 return false;             }              return true;         }     }      public class ESIA_Marker_Answer     {         public string state { get; set; }         public string token_type { get; set; }         public int expires_in { get; set; }         public string refresh_token { get; set; }         public string id_token { get; set; }         public string access_token { get; set; }     } 

Вот в принципе и все. Один запрос, редирект + 2 запроса для получения маркера и информации о пользователе.
В дополнение можно проверять подпись маркера.

ссылка на оригинал статьи https://habrahabr.ru/post/276313/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *