В этой статье я напишу как я прикручивал ЕСИА к сайту, возможны и другие сценарии.
Первым делом надо создать сертификат для генерации подписи в формате 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/
Добавить комментарий