问题
Sorry for my english.
I have three projects: IdentityServer, Ensino.Mvc, Ensino.Api. The IdentityServer Project provides the main identity information and claims - claim Profile, claim Address, claim Sid... etc, from the IdentityServer4 library. The Ensino.Mvc Project gets this information in a token and sends it to the API, so that the MVC is grranted access to the resources. The token contains all the claims provided by IdentityServer. But in the API, I need to generate other claims that are API specific, like: claim EnrollmentId that corresponds to claim Sid from the token. And also I want to add this claim in HttpContext for future purposes. Can somebody tell me how to achieve this?
I have this code in Startup.ConfigureServices:
// Add identity services
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "beehouse.scope.ensino-api";
});
// Add mvc services
services.AddMvc();
In other Project, without API, just mvc, I have inherited UserClaimsPrincipalFactory
and overridden CreateAsync
to add additional claims. I like to do something like this but in the API project. Is it possible?
What the best approach to do this?
EDIT: After some research, what I want to do is: Authentication by IdentityServer and set authorization in api, based on claims and specific api database data.
回答1:
In your API project you can add your own event handler to options.JwtBearerEvents.OnTokenValidated
. This is the point where the ClaimsPrincipal
has been set and you can add claims to the identity or add a new identity to the principal.
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "beehouse.scope.ensino-api";
options.JwtBearerEvents.OnTokenValidated = async (context) =>
{
var identity = context.Principal.Identity as ClaimsIdentity;
// load user specific data from database
...
// add claims to the identity
identity.AddClaim(new Claim("Type", "Value"));
};
});
Note that this will run on every request to the API so it's best to cache the claims if you're loading info from database.
Also, Identity Server should only be responsible for identifying users, not what they do. What they do is application specific (roles, permissions etc.) so you're correct in recognising this and avoiding the logic crossover with Identity Server.
回答2:
Making your own AuthenticationHandler
that uses the IdentityServerAuthenticationHandler
would be the best option. This would allow you to use DI, reject authentication, and skip the custom authentication handler when it is not needed.
Example AuthenticationHandler
that first authenticates the token and then adds more claims:
public class MyApiAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Pass authentication to IdentityServerAuthenticationHandler
var authenticateResult = await Context.AuthenticateAsync("Bearer");
// If token authentication fails, return immediately
if (!authenticateResult.Succeeded)
{
return authenticateResult;
}
// Get user ID from token
var userId = authenticateResult.Principal.Claims
.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject)?.Value;
// Do additional checks for authentication
// e.g. lookup user ID in database
if (userId == null)
{
return AuthenticateResult.NoResult();
}
// Add additional claims
var identity = authenticateResult.Principal.Identity as ClaimsIdentity;
identity.AddClaim(new Claim("MyClaim", "MyValue"));
return authenticateResult;
}
}
Add handler to services:
services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
// ...
})
.AddScheme<AuthenticationSchemeOptions, MyApiAuthenticationHandler>("MyApiScheme", null);
Now you can use either scheme:
// Authenticate token and get extra API claims
[Authorize(AuthenticationSchemes = "MyApiScheme")]
// Authenticate just the token
[Authorize(AuthenticationSchemes = "Bearer")]
Note that IdentityServerAuthenticationHandler
does the same thing, using the dotnet JWT handler:
public class IdentityServerAuthenticationHandler : AuthenticationHandler<IdentityServerAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
...
return await Context.AuthenticateAsync(jwtScheme);
...
}
}
回答3:
OK so step by step:
- You need to create an API Resource(
beehouse.scope.ensino-api
in your case, but I'll recommend you to hide such info when posting code here) in Identity Server. It should be with the same name as youroptions.ApiName
- You need to add this scope to the allowed scopes of your MVC client.
Both steps are described here, but the main thing is when adding the resource you can do something like:
new ApiResource("beehouse.scope.ensino-api", "My test resource", new List<string>() { "claim1", "claim2" });
and then in your client configuration:
new Client
{
ClientId = "client",
.
.
// scopes that client has access to
AllowedScopes = { "beehouse.scope.ensino-api" }
.
.
}
This will add the claims that are associated with this resource to the token. Of course you will have to set this claims on Identity Server level, but from what you said, you already know how to do this.
来源:https://stackoverflow.com/questions/49302420/how-to-add-additional-claims-in-api-project-when-using-identityserver-4