我目前正在转换包含与asp.Net MVC / Web API / Identity后端通信的Angular 9前端组成的现有应用程序。我正在将.Net转换为.Net Core 2.2。后端同时使用cookie和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
}
在邮递员中,我通过传递以下内容来生成令牌:
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"
}
我使用上面的“访问令牌” 和以下标头调用测试Web 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 为空。 我正在使用生成的WebApp2-TestAuth.pfx证书。 这是启动方法,但我将胆量放在单独的静态类中。
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示例。我更改了CreateTicketAsync,如下所示:
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;
//
}
请帮助,我对如何获得有效的不记名令牌感到困惑。