问题描述
早上好。我在控制器中授权和使用角色和/或策略设置时遇到问题。它总是返回 403 forbidden,日志中包含以下内容:
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
The request address matched a server endpoint: Token.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
The token request was successfully extracted: {
"grant_type": "password","username": "Administrator@MRM2Inc.com","password": "[redacted]"
}.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
The token request was successfully validated.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
The response was successfully returned as a JSON document: {
"access_token": "[redacted]","token_type": "Bearer","expires_in": 3600
}.
info: OpenIddict.Validation.OpenIddictValidationdispatcher[0]
The response was successfully returned as a challenge response: {
"error": "insufficient_access","error_description": "The user represented by the token is not allowed to perform the requested action.","error_uri": "https://documentation.openiddict.com/errors/ID2095"
}.
info: OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler[13]
AuthenticationScheme: OpenIddict.Validation.AspNetCore was forbidden.
如果我从授权标签中删除 Roles = 或 Policy = 它会起作用。我的项目设置如下:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<IdentDbContext>(options =>
{
options.UsesqlServer(
Configuration.GetConnectionString("IdentityDB"));
options.USEOpenIddict();
});
// Add the Identity Services we are going to be using the Application Users and the Application Roles
services.AddIdentity<ApplicationUsers,ApplicationRoles>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.SignIn.RequireConfirmedAccount = true;
config.User.RequireUniqueEmail = true;
config.Lockout.MaxFailedAccessAttempts = 3;
}).AddEntityFrameworkStores<IdentDbContext>()
.AddUserStore<ApplicationUserStore>()
.AddRoleStore<ApplicationRoleStore>()
.AddRoleManager<ApplicationRoleManager>()
.AddUserManager<ApplicationUserManager>()
.AddErrorDescriber<ApplicationIdentityErrorDescriber>()
.AddDefaultTokenProviders()
.AddDefaultUI();
services.AddDataLibrary();
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
// Add in the email
var emailConfig = Configuration.GetSection("EmailConfiguration").Get<EmailConfiguration>();
services.AddSingleton(emailConfig);
services.AddEmailLibrary();
services.AddAuthorization(option =>
{
option.AddPolicy("SiteAdmin",policy => policy.RequireClaim("Site Administrator"));
});
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default entities.
options.UseEntityFrameworkCore()
.UseDbContext<IdentDbContext>()
/*.ReplaceDefaultEntities<Guid>()*/;
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the token endpoint. What other endpoints?
options.SetlogoutEndpointUris("/api/logoutPost")
.SetTokenEndpointUris("/Token");
// Enable the client credentials flow. Which flow do I need?
options.AllowPasswordFlow();
options.AcceptAnonymousClients();
options.disableAccesstokenEncryption();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core options.
options.UseAspNetCore()
.EnablelogoutEndpointPassthrough()
.EnabletokenEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
// Register the Swagger generator,defining 1 or more Swagger documents
services.AddSwaggerGen(swagger =>
{
swagger.AddSecurityDeFinition("Bearer",new OpenApiSecurityScheme()
{
Name = "Authorization",Type = SecuritySchemeType.Http,Scheme = "Bearer",BearerFormat = "JWT",In = ParameterLocation.Header,Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer'[space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\""
});
swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,Id = "Bearer"
}
},new string[] {}
}
});
swagger.OperationFilter<SwaggerDefaultValues>();
swagger.OperationFilter<AuthenticationRequirementOperationFilter>();
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory,xmlFile);
swagger.IncludeXmlComments(xmlPath);
});
services.AddApiVersioning();
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVVV";
options.DefaultApiVersion = ApiVersion.Parse("0.6.alpha");
options.AssumeDefaultVersionWhenUnspecified = true;
});
services.AddTransient<IConfigureOptions<SwaggerGenoptions>,ConfigureSwaggerOptions>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,IWebHostEnvironment env,IApiVersionDescriptionProvider provider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios,see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML,JS,CSS,etc.),// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.displayOperationId();
var versionDescription = provider.ApiVersionDescriptions;
foreach (var description in provider.ApiVersionDescriptions.OrderByDescending(_ => _.ApiVersion))
{
c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",$"MRM2 Identity API {description.GroupName}");
}
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
授权控制器.cs
/// <summary>
/// Controls the Authorization aspects of the API
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
[ApiVersion("0.8.alpha")]
[Produces(MediaTypeNames.Application.Json)]
public class AuthorizationController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IdentDbContext _context;
private readonly ApplicationUserManager _userManager;
private readonly ApplicationRoleManager _roleManager;
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly SignInManager<ApplicationUsers> _signInManager;
private HttpClient _client;
public AuthorizationController(IConfiguration configuration,IdentDbContext context,ApplicationUserManager userManager,ApplicationRoleManager roleManager,IOpenIddictApplicationManager applicationManager,IOpenIddictAuthorizationManager authorizationManager,IOpenIddictScopeManager scopeManager,SignInManager<ApplicationUsers> signInManager)
{
_configuration = configuration;
_context = context;
_userManager = userManager;
_roleManager = roleManager;
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_scopeManager = scopeManager;
_signInManager = signInManager;
}
[HttpPost("/token"),Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetopenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
ClaimsPrincipal claimsPrincipal;
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
var roleList = await _userManager.GetRolesListAsync(user);
var databaseList = await _userManager.GetDatabasesAsync(user);
string symKey = _configuration["Jwt:Symmetrical:Key"];
string jwtSub = _configuration["Jwt:Subject"];
string issuer = _configuration["Jwt:Issuer"];
string audience = _configuration["Jwt:Audience"];
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub,jwtSub,issuer),new Claim(ClaimTypes.NameIdentifier,user.Id.ToString(),new Claim(ClaimTypes.Name,user.UserName,issuer)
};
foreach (var role in roleList)
{
claims.Add(new Claim(ClaimTypes.Role,role.Name));
}
foreach (var database in databaseList)
{
claims.Add(new Claim(type: "DatabaseName",database));
}
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(OpenIddictConstants.Claims.Name,OpenIddictConstants.Destinations.Accesstoken);
identity.AddClaim(OpenIddictConstants.Claims.Subject,OpenIddictConstants.Destinations.Accesstoken);
identity.AddClaim(OpenIddictConstants.Claims.Audience,audience,OpenIddictConstants.Destinations.Accesstoken);
foreach (var cl in claims)
{
identity.AddClaim(cl.Type,cl.Value);
}
claimsPrincipal = new ClaimsPrincipal(identity);
// Set the list of scopes granted to the client application.
claimsPrincipal.SetScopes(new[]
{
Scopes.OpenId,Scopes.Email,Scopes.Profile,Scopes.Roles
}.Intersect(request.GetScopes()));
foreach (var claim in claimsPrincipal.Claims)
{
claim.SetDestinations(GetDestinations(claim,claimsPrincipal));
}
return SignIn(claimsPrincipal,OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the authorization code/device code/refresh token.
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
// Retrieve the user profile corresponding to the authorization code/refresh token.
// Note: if you want to automatically invalidate the authorization code/refresh token
// when the user password/roles change,use the following line instead:
// var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
var user = await _userManager.GetUserAsync(principal);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,properties: new AuthenticationProperties(new Dictionary<string,string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim,principal));
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal,OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
private IEnumerable<string> GetDestinations(Claim claim,ClaimsPrincipal principal)
{
// Note: by default,claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them,you must attach them a destination,that specifies
// whether they should be included in access tokens,in identity tokens or in both.
switch (claim.Type)
{
case Claims.Name:
yield return Destinations.Accesstoken;
if (principal.HasScope(Scopes.Profile))
yield return Destinations.IdentityToken;
yield break;
case Claims.Email:
yield return Destinations.Accesstoken;
if (principal.HasScope(Scopes.Email))
yield return Destinations.IdentityToken;
yield break;
case Claims.Role:
yield return Destinations.Accesstoken;
if (principal.HasScope(Scopes.Roles))
yield return Destinations.IdentityToken;
yield break;
// Never include the security stamp in the access and identity tokens,as it's a secret value.
case "AspNet.Identity.SecurityStamp": yield break;
default:
yield return Destinations.Accesstoken;
yield break;
}
}
}
角色控制器.cs
/// <summary>
/// Controls the actions for roles within the API
/// </summary>
/// <response code="401">If the user did not login correctly or
/// does not have the correct permissions</response>
[Route("api/[controller]")]
[ApiController]
[ApiVersion("0.8.alpha")]
[Produces(MediaTypeNames.Application.Json)]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme,Roles = "Site Administrator")] //If I change this to Policy = "SiteAdmin" still does not work. If I remove the Roles completely it works.
public class RolesController : ControllerBase
{
private readonly ApplicationRoleManager _roleManager;
private readonly ILogger<RolesController> _logger;
private readonly IApplicationDatabaseData _databaseData;
public RolesController(ApplicationRoleManager roleManager,ILogger<RolesController> logger,IApplicationDatabaseData databaseData)
{
_roleManager = roleManager;
_logger = logger;
_databaseData = databaseData;
}
/// <summary>
/// Gets a List of all the Roles
/// </summary>
/// <returns>A list of ApplicationRoles</returns>
/// <response code="200">Returns the list</response>
/// <response code="404">If the list is empty</response>
[HttpGet("ListRoles",Name = nameof(ListRolesAsync))]
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme/*,Roles = "Site Administrator"*/)] //Currently commented out until Roles work.
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IList<ApplicationRoles>>> ListRolesAsync()
{
var roles = await _roleManager.GetAllRoles();
if (!roles.Any())
{
return NotFound();
}
else
{
var output = roles;
return Ok(output);
}
}
}
我注意到,在调试中逐步执行此操作时,除了在通过 GetDestinations 时的 Claims.Name 之外,所有声明都达到了默认的 switch case。所以我不确定我在启动或授权控制器中哪里出错了。但我很确定我的问题就在那里。 为了让我的角色和/或政策在控制器内正常工作,我错过了什么?
解决方法
对 AuthorizationController 的更新允许它工作。 Exchange 方法的 AuthorizationContoller 的新部分如下(仍在进行中,但现在正在进行中):
[HttpPost("/token"),Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
ClaimsPrincipal claimsPrincipal;
if (request.IsClientCredentialsGrantType())
{
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(OpenIddictConstants.Claims.Subject,request.ClientId ?? throw new InvalidOperationException());
identity.AddClaim("some-claim","some-value",OpenIddictConstants.Destinations.AccessToken);
claimsPrincipal = new ClaimsPrincipal(identity);
claimsPrincipal.SetScopes(request.GetScopes());
}
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
var roleList = await _userManager.GetRolesListAsync(user);
var databaseList = await _userManager.GetDatabasesAsync(user);
string symKey = _configuration["Jwt:Symmetrical:Key"];
string jwtSub = _configuration["Jwt:Subject"] + " " + user.Id;
string issuer = _configuration["Jwt:Issuer"];
string audience = _configuration["Jwt:Audience"];
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub,jwtSub,issuer),new Claim(ClaimTypes.NameIdentifier,user.Id.ToString(),new Claim(ClaimTypes.Name,user.UserName,issuer)
};
foreach (var role in roleList)
{
claims.Add(new Claim(ClaimTypes.Role,role.Name,ClaimValueTypes.String,issuer));
}
foreach (var database in databaseList)
{
claims.Add(new Claim(type: "DatabaseName",database,issuer));
}
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(OpenIddictConstants.Claims.Name,OpenIddictConstants.Destinations.AccessToken);
identity.AddClaim(OpenIddictConstants.Claims.Subject,OpenIddictConstants.Destinations.AccessToken);
identity.AddClaim(OpenIddictConstants.Claims.Audience,audience,OpenIddictConstants.Destinations.AccessToken);
foreach (var cl in claims)
{
if (cl.Type == ClaimTypes.Role)
{
identity.AddClaim(OpenIddictConstants.Claims.Role,cl.Value,OpenIddictConstants.Destinations.AccessToken,OpenIddictConstants.Destinations.IdentityToken);
}
identity.AddClaim(cl.Type,OpenIddictConstants.Destinations.IdentityToken);
}
claimsPrincipal = new ClaimsPrincipal(identity);
// Set the list of scopes granted to the client application.
claimsPrincipal.SetScopes(new[]
{
Scopes.OpenId,Scopes.Email,Scopes.Profile,Scopes.Roles
}.Intersect(request.GetScopes()));
foreach (var claim in claimsPrincipal.Claims)
{
claim.SetDestinations(GetDestinations(claim,claimsPrincipal));
}
return SignIn(claimsPrincipal,OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the authorization code/device code/refresh token.
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
// Retrieve the user profile corresponding to the authorization code/refresh token.
// Note: if you want to automatically invalidate the authorization code/refresh token
// when the user password/roles change,use the following line instead:
// var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
var user = await _userManager.GetUserAsync(principal);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,properties: new AuthenticationProperties(new Dictionary<string,string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim,principal));
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal,OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}