В настоящее время я работаю над преобразованием существующего приложения, состоящего из интерфейса Angular 9, с Asp. Net MVC / Web API / Identity backend. Я конвертирую. Net в. Net Core 2.2. Бэкэнд использует как куки, так и аутентификацию и авторизацию JWT. Я перехожу к OpenId Dict / Identity и OpenId Connect. К счастью, я создал отдельный проект для тестирования решения, потому что оно было пробным и много ошибок. Мне очень не хватает понимания OpenId Dict / Connect.
Мне нужна помощь в создании действительного токена на предъявителя. Токен, который выдает проверенного пользователя.
Хорошо известны следующие значения:
{
"issuer": "https://localhost:44324/",
"token_endpoint": "https://localhost:44324/connect/token",
"end_session_endpoint": "https://localhost:44324/connect/logout",
"userinfo_endpoint": "https://localhost:44324/api/userinfo",
"jwks_uri": "https://localhost:44324/.well-known/jwks",
"grant_types_supported": [
"password"
],
"scopes_supported": [
"openid",
"email",
"profile",
"roles"
],
"claims_supported": [
"aud",
"exp",
"iat",
"iss",
"jti",
"sub"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"subject_types_supported": [
"public"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"claims_parameter_supported": false,
"request_parameter_supported": false,
"request_uri_parameter_supported": false
}
В Postman я генерирую токен, передавая следующее:
KEY VALUE
grant_type password
username Phil
password P@ssw0rd
client_id incidentservices
scope openid profile email roles
Он генерирует следующее:
{
"token_type": "Bearer",
"access_token": "CfDJ8ISU_Npa_VFIjdjKglRsdyVxwkfPoTyt7Bfx9UPtjrl57djzvm9VDvgNwFbJj-O_op4Mjl2AyecK0bBgb1o3B5p2G3TNdpXyPyQocLz-4zz2lROBAv-M8XsDvaJHEXE234YVqcQcju2Rp80MS7cVhBIrQVdaA94CgF_DE662o-i5wMrod2XACzUDdqDMgLJe9whr5RsIdsAmdCMHoK_yZHEKg68SgBJiSSXok8b35FRwDu1KEDY6cPht0FjBO02XFdsSnRJksBunx6eyif4vReh7-Q09BrQ0F1tk2nnZ3RxB2ttDKdpiZmcgQ3UAVWwxo1psAEkV6yRcroG1cKWM530APP2FLqa52wVzYr-AsESGEIiMKNaypK6Z7LWMnjV3Cu0F0lfxFJLPAO1A2uA2wtDphhvwNRBQafzbFYyEnMfFEQ8NkQ9nOSkiUQ2EbPB34qmiU2IEZijFJT1h5ZvbovCGP8qzslv6O2QUfJkDO0RlymqJNQZeoNPI4rwCyIbUZ9kP3rdoiOcrBjbQdKd5BiCAVQ4GHXOwYj2Oq61foPZByK0ek0imna22yybPYdMsUawHv12WY4Y57DIsFx4cLl8n2d0rjiE20a7rhNjjlzmofsN_jfN8e1JBnMBq0YgDkjASr2t-JcT-EQME16dQx2nVNqnZt95EYfcJxuMugR4pDZ18HJNnpRqx-PnhZapUryVqFMfPHuEEVewX0qpYPPlK6z-DMmGLxynukCC6aO1O70D_FgdemlmeWYViIXa11NHQscBobgfHAdYyA8NCKbVhUyK5R_hN7Ult5vMxVLmWAD25q1_DVwR-7gX7fZWwLuTcOJCVCZeZ9pldxrclL6zNJdHuh4B9_pGq_cN7EomF9Pyq0bM74-LPhBaKfYpVBnNIN6133jjXGFa1cOiS0fvJ3n54LEXXjsGM1_jAV7kOfT0ORwJOz_chIsE7fMgUTNZtUztjAlc-rkoQcPADBgTNs_fFhPM5857qMOBbMwL-",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjgyQ0Q2NjdCOUQxRjNENjY0OTVBMENENTBCNTZCNkZCMTk5MjlDNjIiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiI3MTM0NkQ2Mi05QkE1LTRCNkQtOUVDQS03NTU1NzRENjI4RDgiLCJ0b2tlbl91c2FnZSI6ImlkX3Rva2VuIiwianRpIjoiOTAxNWIzYmEtMjEwYS00MzA5LThhZjgtNmRmZmU2YTJhNThmIiwiYXVkIjoiaW5jaWRlbnRzZXJ2aWNlcyIsImF0X2hhc2giOiJaRUtWZk9FNjF5SFdVdVVQeUQ1MXRnIiwiYXpwIjoiaW5jaWRlbnRzZXJ2aWNlcyIsIm5iZiI6MTU4MTk3MjI3NiwiZXhwIjoxNTgxOTczNDc2LCJpYXQiOjE1ODE5NzIyNzYsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0MzI0LyJ9.sr48DAD2FWfPasKEioAFPZsXLEaRaubft4IIqj8uIG4dUfSFc59Je_q0FbwwA_HmHYVPnxga5yy_aBFTyAdTDyP1-2fTHdtnYau65_M5TbIupMETR6lLOB40Q51P2Nah83uOc6d_DAWiRQyI7q7AG3gRYewSU8QYoVDeAc8HKHIlnsp-HDSKlqcQScl25OqH7EcF-kM9zJChZFHroxc7kduYJyYpWad9081oqnXCyAkJK_R2g_EZKszBdvPTj4G3Wv29221lwLfNn1Dt7GTPkxaCaitWzXd708GR3xLAAwL4t6ENJz_CvtiPjS5lIPTjRlx8UHm9CztWdaFFgU-kQA"
}
Я вызываю тестовый веб-API, используя приведенный выше 'токен доступа' со следующим заголовком:
KEY VALUE
Authorization Bearer CfDJ8ISU_Npa_VFIjdjKglRsdyVxwkfPoTyt7Bfx9UPtjrl57djzvm9VDvgNwFbJj-O_op4Mjl2AyecK0bBgb1o3B5p2G3TNdpXyPyQocLz-4zz2lROBAv-M8XsDvaJHEXE234YVqcQcju2Rp80MS7cVhBIrQVdaA94CgF_DE662o-i5wMrod2XACzUDdqDMgLJe9whr5RsIdsAmdCMHoK_yZHEKg68SgBJiSSXok8b35FRwDu1KEDY6cPht0FjBO02XFdsSnRJksBunx6eyif4vReh7-Q09BrQ0F1tk2nnZ3RxB2ttDKdpiZmcgQ3UAVWwxo1psAEkV6yRcroG1cKWM530APP2FLqa52wVzYr-AsESGEIiMKNaypK6Z7LWMnjV3Cu0F0lfxFJLPAO1A2uA2wtDphhvwNRBQafzbFYyEnMfFEQ8NkQ9nOSkiUQ2EbPB34qmiU2IEZijFJT1h5ZvbovCGP8qzslv6O2QUfJkDO0RlymqJNQZeoNPI4rwCyIbUZ9kP3rdoiOcrBjbQdKd5BiCAVQ4GHXOwYj2Oq61foPZByK0ek0imna22yybPYdMsUawHv12WY4Y57DIsFx4cLl8n2d0rjiE20a7rhNjjlzmofsN_jfN8e1JBnMBq0YgDkjASr2t-JcT-EQME16dQx2nVNqnZt95EYfcJxuMugR4pDZ18HJNnpRqx-PnhZapUryVqFMfPHuEEVewX0qpYPPlK6z-DMmGLxynukCC6aO1O70D_FgdemlmeWYViIXa11NHQscBobgfHAdYyA8NCKbVhUyK5R_hN7Ult5vMxVLmWAD25q1_DVwR-7gX7fZWwLuTcOJCVCZeZ9pldxrclL6zNJdHuh4B9_pGq_cN7EomF9Pyq0bM74-LPhBaKfYpVBnNIN6133jjXGFa1cOiS0fvJ3n54LEXXjsGM1_jAV7kOfT0ORwJOz_chIsE7fMgUTNZtUztjAlc-rkoQcPADBgTNs_fFhPM5857qMOBbMwL-
И используя точку останова, я проверяю User.Identity , IsAuthenticated - false, а Name - null. Я использую сертификат WebApp2-TestAuth.pfx, который я сгенерировал. Это метод запуска, но я поместил кишки в отдельный класс c.
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
//
public void ConfigureServices(IServiceCollection services)
{
//
AuthSettings _authSettings = new AuthSettings();
_authSettings = Options.Create<AuthSettings>(
Configuration.GetSection("AuthSettings").Get<AuthSettings>()).Value;
services.AddSingleton<AuthSettings>(_authSettings);
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSingleton<IEmailSender, NotificationService>();
services.AddCors();
ConfigureDatabase(services);
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Stores.MaxLengthForKeys = 128;
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
// Register the OpenIddict services.
OpenIdDictStartup.OpenIdDictStartupServices(services, _authSettings);
services.AddMvc(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
});
//
}
//
public virtual void ConfigureDatabase(IServiceCollection services)
{
string _connetionString = Configuration.GetConnectionString("DefaultConnection");
if (string.IsNullOrEmpty(_connetionString))
{
throw (new ApplicationException("no connection string found"));
}
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(_connetionString);
options.UseOpenIddict();
});
}
//
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
OpenIdDictStartup.OpenIdDictConfigure(app, env);
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
OpenIdDictStartup.InitializeOpenIddictApplicationAsync(
app.ApplicationServices).GetAwaiter().GetResult();
//
}
}
Это кишки кода OpenId Dict / Connect:
using System;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
//
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using AspNet.Security.OpenIdConnect.Primitives;
//
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore.Models;
//
using WebApp02.Data;
using WebApp02.Models;
//
namespace WebApp02.OpenIddict
{
public static class OpenIdDictStartup
{
//
public static void OpenIdDictStartupServices(IServiceCollection services, AuthSettings authSettings)
{
//
X509Certificate2 _jwtSigningCert = new X509Certificate2(authSettings.CertLocation, authSettings.CertPassword);
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
.AddServer(options =>
{
options.UseMvc();
options.EnableTokenEndpoint("/connect/token");
options.EnableLogoutEndpoint("/connect/logout");
options.EnableUserinfoEndpoint("/api/userinfo");
options.AllowPasswordFlow();
options.AddSigningCertificate(_jwtSigningCert);
options.DisableHttpsRequirement();
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
});
DualCookieJWTStartupServicesAuthentication(services, authSettings);
//
}
//
public static void DualCookieJWTStartupServicesAuthentication(IServiceCollection services, AuthSettings authSettings)
{
//
X509Certificate2 _jwtSigningCert = new X509Certificate2(authSettings.CertLocation, authSettings.CertPassword);
services.AddAuthentication()
.AddCookie(options =>
{
options.SlidingExpiration = true;
options.LoginPath = new PathString("/Identity/Account/Login");
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddJwtBearer(options =>
{
options.Authority = authSettings.JwtAuthority;
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = authSettings.JwtIssuer,
ValidateIssuer = true,
ValidAudience = authSettings.JwtAudience,
IssuerSigningKey = new X509SecurityKey(_jwtSigningCert),
ValidateIssuerSigningKey = true,
};
});
services.AddAuthorization(options =>
{
var _policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, CookieAuthenticationDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
options.AddPolicy("DualCookieJWT", _policy);
});
//
}
//
public static void OpenIdDictConfigure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
}
//
public static async Task InitializeOpenIddictApplicationAsync(IServiceProvider services)
{
//
string _clientId = "incidentservices";
using (var _scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
try
{
var _dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await _dbContext.Database.EnsureCreatedAsync();
var _openIddictManager = _scope.ServiceProvider.GetRequiredService<OpenIddictApplicationManager<OpenIddictApplication>>();
if (await _openIddictManager.FindByClientIdAsync(_clientId) == null)
{
var _descriptor = new OpenIddictApplicationDescriptor
{
ClientId = _clientId,
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Logout,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.Password,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles
}
};
await _openIddictManager.CreateAsync(_descriptor);
}
}
catch (Exception _ex)
{
Console.WriteLine(_ex.ToString());
}
//
}
}
}
}
Контроллер AuthorizationController был взят из образца ядра OpenIddict версии 2.0.1 Mvc .Server. Я изменил CreateTicketAsyn c, поэтому он выглядит следующим образом:
private async Task<AuthenticationTicket> CreateTicketAsync(
OpenIdConnectRequest request, ApplicationUser user,
AuthenticationProperties properties = null)
{
var _principal = await _signInManager.CreateUserPrincipalAsync(user);
var _ticket = new AuthenticationTicket(_principal, properties,
OpenIddictServerDefaults.AuthenticationScheme);
if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
{
_ticket.SetScopes(request.GetScopes());
_ticket.SetResources("resource_server");
}
var _identity = (ClaimsIdentity)_principal.Identity;
if (_ticket.HasScope(OpenIdConnectConstants.Scopes.Profile))
{
var _subject = new Claim(OpenIdConnectConstants.Claims.Subject,
_authSettings.OIDCSubject,
OpenIdConnectConstants.Destinations.AccessToken);
_identity.AddClaim(_subject);
//
var _name = new Claim(OpenIdConnectConstants.Claims.Username, user.UserName);
_name.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
_identity.AddClaim(_name);
//
var _uname = new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName);
_uname.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
_identity.AddClaim(_uname);
}
IList<string> _roles = await _userManager.GetRolesAsync(user);
foreach (string _role in _roles)
{
_identity.AddClaim(new Claim("roles", _role));
}
foreach (var _claim in _ticket.Principal.Claims)
{
_claim.SetDestinations(GetDestinations(_claim, _ticket));
}
return _ticket;
//
}
Пожалуйста, помогите, я не знаю, как получить токен на предъявителя.