рдкреНрд░рд╕реНрддрд╛рд╡рдирд╛
рдПрдХ рдмрд╛рд░ рджреВрд░, рджреВрд░ рдХреА рдЖрдХрд╛рд╢рдЧрдВрдЧрд╛ рдореЗрдВ ... рдпрд╣ рд╣рдореЗрдВ GosUslugi рдкрд░ рдПрдХ ESIA рдЦрд╛рддреЗ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдкреНрд░рдорд╛рдгрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓реЗ рдЧрдпрд╛ред рдЪреВрдВрдХрд┐ рд╣рдо .Net рдЖрдХрд╛рд╢рдЧрдВрдЧрд╛ рдореЗрдВ рд░рд╣рддреЗ рд╣реИрдВ, рдкрд╣рд▓реА рдЪреАрдЬ рдЬрд┐рд╕рдХрд╛ рд╣рдордиреЗ рдЕрдзреНрдпрдпрди рдХрд┐рдпрд╛ рдерд╛ рд╡рд╣ рдПрдХ рддреИрдпрд╛рд░ рдЕрдВрддрд░рд┐рдХреНрд╖ рдпрд╛рди рдХреЗ рд▓рд┐рдП рдкреВрд░рд╛ рдЧреЛрдЧреЛрд▓ рдерд╛ рддрд╛рдХрд┐ рдЦреБрдж рд╕реЗ рд╕рдм рдХреБрдЫ рди рдЙрдЧрд▓ рджрд┐рдпрд╛ рдЬрд╛рдП, рд▓реЗрдХрд┐рди рдЦреЛрдЬреЛрдВ рд╕реЗ рдХреБрдЫ рднреА рд╕рд╛рд░реНрдердХ рдирд╣реАрдВ рд╣реБрдЖред рдЗрд╕рд▓рд┐рдП, рд╡рд┐рд╖рдп рдХрд╛ рдЕрдзреНрдпрдпрди рдХрд░рдиреЗ рдФрд░ рдЕрдкрдиреЗ рджрдо рдкрд░ рдПрдХ рд╣реА рд╕реНрдкреЗрд╕рд╢рд┐рдк рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХрд╛ рдирд┐рд░реНрдгрдп рд▓рд┐рдпрд╛ рдЧрдпрд╛ред

рдкрд░рд┐рдЪрдп
, , , тАФ , . , .
... , . , :
3 , 2 тАУ , . 2 : SAML OpenID Connect. ,
01.01.2018 . SAML 2.0 ( ). OAuth 2.0 / OpenID Connect ( ).
. , :
- - - ┬л ┬╗
- -
- -
- .
Windows, CSP. docker Linux- , . :
- .Net Framework 4.8
- CSP, .Net
- , ( 1 )
client_secret, 4 UTF-8:
- Scope ( , , ). , ┬лfullname gender email mobile usr_org┬╗
- Timestamp ( ┬лyyyy.MM.dd HH:mm:ss +0000┬╗)
- ClientId ( , )
- State ( , Guid.NewGuid().ToString(┬лD┬╗))
private string GetClientSecret(
X509Certificate2 certificate,
string scope,
string timestamp,
string clientId,
string state)
{
var signMessage = Encoding.UTF8.GetBytes($"{scope}{timestamp}{clientId}{state}");
byte[] encodedSignature = SignatureProvider.Sign(signMessage, certificate);
return Base64UrlEncoder.Encode(encodedSignature);
}
SignatureProvider тАУ , . тАУ .
1-4: (EsiaAuthUrl). ( ) url тАУ https://esia-portal1.test.gosuslugi.ru/aas/oauth2/ac
. :
{EsiaAuthUrl}?client_id={ClientId}&scope={Scope}&response_type=code&state={State}& timestamp={Timestamp}&access_type=online&redirect_uri={RedirectUri}&client_secret={ClientSecret}
рдЬрд╣рд╛рдБ RedirectUri рд╡рд╣ рдкрддрд╛ рд╣реИ рдЬрд┐рд╕ рдкрд░ ESIA рдХреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреЛ рдирд┐рд░реНрджреЗрд╢рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛, рдФрд░ рдХреНрд▓рд╛рдЗрдВрдЯрд╕реНрдХреНрд░реЗрдЯ GetClientSecret рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдкрд░рд┐рдгрд╛рдо рд╣реИред рдЕрдиреНрдп рдорд╛рдкрджрдВрдбреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдкрд╣рд▓реЗ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд╣рдо URL рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ, рд╡рд╣рд╛рдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдкреБрдирд░реНрдирд┐рд░реНрджреЗрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдПрдХ рд▓реЙрдЧрд┐рди рдкрд╛рд╕рд╡рд░реНрдб рджрд░реНрдЬ рдХрд░рддрд╛ рд╣реИ, рдЖрдкрдХреЗ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд▓рд┐рдП рдЙрд╕рдХреЗ рдбреЗрдЯрд╛ рддрдХ рдкрд╣реБрдВрдЪ рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░рддрд╛ рд╣реИред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдИрдПрд╕рдЖрдИрдП рдЖрдкрдХреЗ рд╕рд┐рд╕реНрдЯрдо рдХреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ RedirectUri рдкрддреЗ рдкрд░ рднреЗрдЬрддрд╛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЛрдб рд╣реЛрддрд╛ рд╣реИред рд╣рдореЗрдВ рдИрдПрд╕рдЖрдИрдП рдореЗрдВ рдЖрдЧреЗ рдХреА рдкреВрдЫрддрд╛рдЫ рдХреЗ рд▓рд┐рдП рдЗрд╕ рдХреЛрдб рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреАред
рдПрдХ рдкрд╣реБрдВрдЪ рдЯреЛрдХрди рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛
рдИрдПрд╕рдЖрдИрдП рдореЗрдВ рдХреЛрдИ рднреА рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдПрдХ рдПрдХреНрд╕реЗрд╕ рдЯреЛрдХрди рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо https://esia-portal1.test.gosuslugi.ru/aas/oauth2/te
ESIA рдореЗрдВ рдПрдХ POST рдЕрдиреБрд░реЛрдз рдмрдирд╛рддреЗ рд╣реИрдВ (рдПрдХ рдкрд░реАрдХреНрд╖рдг рд╡рд╛рддрд╛рд╡рд░рдг рдХреЗ рд▓рд┐рдП, рдмреЗрд╕ url рд╣реИ: - EsiaTokenUrl)ред рдпрд╣рд╛рдВ рдЕрдиреБрд░реЛрдз рдХреЗ рдореБрдЦреНрдп рдХреНрд╖реЗрддреНрд░ рд╕рдорд╛рди рддрд░реАрдХреЗ рд╕реЗ рдмрдирд╛рдП рдЧрдП рд╣реИрдВ, рдХреЛрдб рдореЗрдВ рдЖрдкрдХреЛ рдирд┐рдореНрди рдЬреИрд╕рд╛ рдХреБрдЫ рдорд┐рд▓рддрд╛ рд╣реИ:
public async Task<EsiaAuthToken> GetAccessToken(
string code,
string callbackUrl = null,
X509Certificate2 certificate = null)
{
var timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");
var state = Guid.NewGuid().ToString("D");
var clientSecret = GetClientSecret(
certificate,
Configuration.Scope,
timestamp,
Configuration.ClientId,
state);
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", Configuration.ClientId),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("state", state),
new KeyValuePair<string, string>("scope", Configuration.Scope),
new KeyValuePair<string, string>("timestamp", timestamp),
new KeyValuePair<string, string>("token_type", "Bearer"),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("redirect_uri", callbackUrl)
};
using (var client = new HttpClient())
using (var response = await client.PostAsync(Configuration.EsiaTokenUrl, new FormUrlEncodedContent(requestParams)))
{
response.EnsureSuccessStatusCode();
var tokenResponse = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<EsiaAuthToken>(tokenResponse);
Argument.NotNull(token?.AccessToken, " ");
Argument.Require(state == token.State, " ");
return token;
}
}
( Configuration). , code , . :
public class EsiaAuthToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
public string State { get; set; }
public EsiaAuthTokenPayload Payload
{
get
{
if (string.IsNullOrEmpty(AccessToken))
{
return null;
}
string[] parts = AccessToken.Split('.');
if (parts.Length < 2)
{
throw new System.Exception($" . : {AccessToken}");
}
var payload = Encoding.UTF8.GetString(Base64UrlEncoder.Decode(parts[1]));
return JsonConvert.DeserializeObject<EsiaAuthTokenPayload>(payload);
}
}
}
public class EsiaAuthTokenPayload
{
[JsonProperty("urn:esia:sid")]
public string TokenId { get; set; }
[JsonProperty("urn:esia:sbj_id")]
public string UserId { get; set; }
}
. . GET REST API , url (EsiaRestUrl) : https://esia-portal1.test.gosuslugi.ru/rs
. , :
public async Task<EsiaUser> GetUser(string userId, string accessToken)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetStringAsync($"{Configuration.EsiaRestUrl}/prns/{userId}?embed=(organizations)");
var user = JsonConvert.DeserializeObject<EsiaUser>(response);
user.Id = user.Id ?? userId;
return user;
}
}
тАУ . , . , . , :
public class EsiaUser
{
[JsonProperty("oid")]
public string Id { get; set; }
[JsonProperty("firstName")]
public string FirstName { get; set; }
[JsonProperty("lastName")]
public string LastName { get; set; }
[JsonProperty("middleName")]
public string MiddleName { get; set; }
[JsonProperty("trusted")]
public bool Trusted { get; set; }
[JsonProperty("organizations")]
public EsiaUserOrganizations OrganizationLinks { get; set; }
}
public class EsiaUserOrganizations
{
[JsonProperty("elements")]
public List<string> Links { get; set; }
}
, .. scope. . . , , State . scope scopeтАЩ , , :
http:
public async Task<EsiaAuthToken> GetOrganizationAccessToken(
string organizationId,
string code,
string state,
string callbackUrl = null,
X509Certificate2 certificate = null)
{
var timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");
var scope = string.Join(" ", Configuration.OrganizationScope.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(orgScope => $"{Configuration.EsiaBaseUrl}/{orgScope}?org_oid={organizationId}"));
var clientSecret = GetClientSecret(
certificate,
scope,
timestamp,
Configuration.ClientId,
state);
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", Configuration.ClientId),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("state", state),
new KeyValuePair<string, string>("scope", scope),
new KeyValuePair<string, string>("timestamp", timestamp),
new KeyValuePair<string, string>("token_type", "Bearer"),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("redirect_uri", callbackUrl)
};
using (var client = new HttpClient())
using (var response = await client.PostAsync(Configuration.EsiaTokenUrl, new FormUrlEncodedContent(requestParams)))
{
response.EnsureSuccessStatusCode();
var tokenResponse = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<EsiaAuthToken>(tokenResponse);
Argument.NotNull(token?.AccessToken, " ");
Argument.Require(state == token.State, " ");
return token;
}
}
, .
public async Task<EsiaOrganization> GetOrganization(string organizationLink, string accessToken)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetStringAsync(organizationLink);
var organization = JsonConvert.DeserializeObject<EsiaOrganization>(response);
return organization;
}
}
:
var accessToken = await IntegrationService.GetAccessToken(request.Code, request.CallbackUrl);
var user = await IntegrationService.GetUser(accessToken.Payload.UserId, accessToken.AccessToken);
if (user.OrganizationLinks?.Links?.Any() == true)
{
var link = user.OrganizationLinks.Links.First();
var organizationId = link.Split('/').Last();
var organizationAccessToken = await IntegrationService.GetOrganizationAccessToken(organizationId, request.Code, accessToken.State, request.CallbackUrl);
user.Organization = await IntegrationService.GetOrganization(link, organizationAccessToken.AccessToken);
}
return user;
рд╢рд╛рдпрдж рдпрд╣ рдИрдПрд╕рдЖрдИрдП рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХреЗ рдмреБрдирд┐рдпрд╛рджреА рдкрд░рд┐рджреГрд╢реНрдп рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рд╣реИред рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдпрджрд┐ рдЖрдк рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреА рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдХреЛ рдЬрд╛рдирддреЗ рд╣реИрдВ, рддреЛ рдИрдПрд╕рдЖрдИрдП рдХреЗ рд▓рд┐рдП рд╕рд┐рд╕реНрдЯрдо рдХрд╛ рд╕реЙрдлреНрдЯрд╡реЗрдпрд░ рдХрдиреЗрдХреНрд╢рди 1 рджрд┐рди рд╕реЗ рдЕрдзрд┐рдХ рдирд╣реАрдВ рд▓реЗрдЧрд╛ред рдпрджрд┐ рдЖрдкрдХреЗ рдХреЛрдИ рдкреНрд░рд╢реНрди рд╣реИрдВ, рддреЛ рдЯрд┐рдкреНрдкрдгреА рдореЗрдВ рдЖрдкрдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИред рдореЗрд░реА рдкреЛрд╕реНрдЯ рдХреЛ рдЕрдВрдд рддрдХ рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж, рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпрд╣ рдЙрдкрдпреЛрдЧреА рд╣реЛрдЧрд╛ред