.Net рдХреЗ рд▓рд┐рдП рдИрдПрд╕рдЖрдИрдП рдХреЗ рд╕рд╛рде рдПрдХреАрдХрд░рдг: рдЬрд┐рддрдирд╛ рд▓рдЧрддрд╛ рд╣реИ рдЙрд╕рд╕реЗ рдЕрдзрд┐рдХ рдЖрд╕рд╛рди рд╣реИ

рдкреНрд░рд╕реНрддрд╛рд╡рдирд╛


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





рдкрд░рд┐рдЪрдп


, , , тАФ , . , .


... , . , :



3 , 2 тАУ , . 2 : SAML OpenID Connect. ,

01.01.2018 . SAML 2.0 ( ). OAuth 2.0 / OpenID Connect ( ).


. , :


  1. - - ┬л ┬╗
  2. -
  3. -
  4. .



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/teESIA рдореЗрдВ рдПрдХ POST рдЕрдиреБрд░реЛрдз рдмрдирд╛рддреЗ рд╣реИрдВ (рдПрдХ рдкрд░реАрдХреНрд╖рдг рд╡рд╛рддрд╛рд╡рд░рдг рдХреЗ рд▓рд┐рдП, рдмреЗрд╕ url рд╣реИ: - EsiaTokenUrl)ред рдпрд╣рд╛рдВ рдЕрдиреБрд░реЛрдз рдХреЗ рдореБрдЦреНрдп рдХреНрд╖реЗрддреНрд░ рд╕рдорд╛рди рддрд░реАрдХреЗ рд╕реЗ рдмрдирд╛рдП рдЧрдП рд╣реИрдВ, рдХреЛрдб рдореЗрдВ рдЖрдкрдХреЛ рдирд┐рдореНрди рдЬреИрд╕рд╛ рдХреБрдЫ рдорд┐рд▓рддрд╛ рд╣реИ:

/// <summary>
///   
/// </summary>
/// <param name="code">     </param>
/// <param name="callbackUrl">     </param>
/// <param name="certificate">   </param>
/// <returns> </returns>
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");

	// Create signature in PKCS#7 detached signature UTF-8
	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 , . :


/// <summary>
///      
/// </summary>
public class EsiaAuthToken
{
	/// <summary>
	///  
	/// </summary>
	[JsonProperty("access_token")]
	public string AccessToken { get; set; }

	/// <summary>
	///  
	/// </summary>
	public string State { get; set; }

	/// <summary>
	///    
	/// </summary>
	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);
		}
	}
}

/// <summary>
///    
/// </summary>
public class EsiaAuthTokenPayload
{
	/// <summary>
	///  
	/// </summary>
	[JsonProperty("urn:esia:sid")]
	public string TokenId { get; set; }

	/// <summary>
	///  
	/// </summary>
	[JsonProperty("urn:esia:sbj_id")]
	public string UserId { get; set; }
}


. . GET REST API , url (EsiaRestUrl) : https://esia-portal1.test.gosuslugi.ru/rs. , :


/// <summary>
///   
/// </summary>
/// <param name="userId"> </param>
/// <param name="accessToken"> </param>
/// <returns> </returns>
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;
	}
}

тАУ . , . , . , :


/// <summary>
///  
/// </summary>
public class EsiaUser
{
	/// <summary>
	/// 
	/// </summary>
	[JsonProperty("oid")]
	public string Id { get; set; }

	/// <summary>
	/// 
	/// </summary>
	[JsonProperty("firstName")]
	public string FirstName { get; set; }

	/// <summary>
	/// 
	/// </summary>
	[JsonProperty("lastName")]
	public string LastName { get; set; }

	/// <summary>
	/// 
	/// </summary>
	[JsonProperty("middleName")]
	public string MiddleName { get; set; }

	/// <summary>
	///   
	/// </summary>
	[JsonProperty("trusted")]
	public bool Trusted { get; set; }

	/// <summary>
	///   
	/// </summary>
	[JsonProperty("organizations")]
	public EsiaUserOrganizations OrganizationLinks { get; set; }
}

/// <summary>
///   
/// </summary>
public class EsiaUserOrganizations
{
	[JsonProperty("elements")]
	public List<string> Links { get; set; }
}


, .. scope. . . , , State . scope scopeтАЩ , , :


http://esia.gosuslugi.ru/org_shortname/?org_oid={organizationId} http://esia.gosuslugi.ru/ org_fullname/?org_oid={organizationId}

/// <summary>
///       
/// </summary>
/// <param name="organizationId"> </param>
/// <param name="code"> </param>
/// <param name="state"> </param>
/// <param name="callbackUrl">     </param>
/// <param name="certificate">   </param>
/// <returns>     </returns>
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}"));

	// Create signature in PKCS#7 detached signature UTF-8
	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;
	}
}

, .


/// <summary>
///   
/// </summary>
/// <param name="organizationLink">  </param>
/// <param name="accessToken"> </param>
/// <returns> </returns>
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 рджрд┐рди рд╕реЗ рдЕрдзрд┐рдХ рдирд╣реАрдВ рд▓реЗрдЧрд╛ред рдпрджрд┐ рдЖрдкрдХреЗ рдХреЛрдИ рдкреНрд░рд╢реНрди рд╣реИрдВ, рддреЛ рдЯрд┐рдкреНрдкрдгреА рдореЗрдВ рдЖрдкрдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИред рдореЗрд░реА рдкреЛрд╕реНрдЯ рдХреЛ рдЕрдВрдд рддрдХ рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж, рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпрд╣ рдЙрдкрдпреЛрдЧреА рд╣реЛрдЧрд╛ред


All Articles