问题
I tried to implement IdentityServer as it is explained at https://aspnetboilerplate.com/Pages/Documents/Zero/Identity-Server
But the sample does not work.
I started a Core 2.0 Angular project from ASP.NET Boilerplate. Is there any updated working sample based on the documentation?
There is more than one problem, but one of them is with AuthConfigurer.cs.
The API caller (client) cannot pass the token validation.
Actually, there is a token generation code in TokenAuthController.cs:
private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
{
var now = DateTime.UtcNow;
var jwtSecurityToken = new JwtSecurityToken(
issuer: _configuration.Issuer,
audience: _configuration.Audience,
claims: claims,
notBefore: now,
expires: now.Add(expiration ?? _configuration.Expiration),
signingCredentials: _configuration.SigningCredentials
);
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
But in the Startup
class, AddIdentity
and AddAuthentication
create different token values and validation rules.
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
.AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
.AddInMemoryClients(IdentityServerConfig.GetClients())
.AddAbpPersistedGrants<IAbpPersistedGrantDbContext>()
.AddAbpIdentityServer<User>(); ;
services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer", options =>
{
options.Authority = "http://localhost:62114/";
options.RequireHttpsMetadata = false;
});
The token can be generated by both sides. CreateAccessToken
is called by the Angular client and by the API client as shown below:
var disco = await DiscoveryClient.GetAsync("http://localhost:21021");
var httpHandler = new HttpClientHandler();
httpHandler.CookieContainer.Add(new Uri("http://localhost:21021/"), new Cookie(MultiTenancyConsts.TenantIdResolveKey, "1")); //Set TenantId
var tokenClient = new TokenClient(disco.TokenEndpoint, "AngularSPA", "secret", httpHandler);
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("admin", "123qwe", "default-api"); //RequestClientCredentialsAsync("default-api");
But just one of them (according to the Authentication part) cannot pass authentication.
I need both the API client authentication and the Angular client authentication to work.
I have some clue from a link about dual authentication:
https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2
But I could not solve this. Any comment is very valuable to solve the problem.
回答1:
At the and I managed to solve problem here are the needed modifications;
1- in the TokenAuthController there is a token creation code as shown below;
private static List<Claim> CreateJwtClaims(ClaimsIdentity identity)
{
var claims = identity.Claims.ToList();
var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier);
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
claims.AddRange(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
});
return claims;
}
If you start using Identityserver Claims coming from login is totally different from current implementation and "sub" claim is already added to claims. So it is not necessary to add seperately. So please update this as shown below
private static List<Claim> CreateJwtClaims(ClaimsIdentity identity)
{
var claims = identity.Claims.ToList();
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
claims.AddRange(new[]
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
});
return claims;
}
2- Add Authentcation to startup class as shown below; The most important part is authenticationSchemaName "IdentityBearer" do not forget adding it.
services.AddAuthentication().AddIdentityServerAuthentication("IdentityBearer", options =>
{
options.Authority = "http://localhost:21021/";
options.RequireHttpsMetadata = false;
});
3- But this is not enough. because if you look at configure methon in startup authontication is registered as
app.UseJwtTokenMiddleware();
if you check it it uses "bearer" schema not IdentityBearer as we added above. So we also need anpther authenticaiton registration. Add this line too (have both of them)
app.UseJwtTokenMiddleware("IdentityBearer");
4- But as you can see there is no method taking string parameter to add UseJwtTokenMiddleware so it is needed to update that class to. please change your class as shown below;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
namespace MyProject.Authentication.JwtBearer
{
public static class JwtTokenMiddleware
{
public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app)
{
return UseJwtTokenMiddleware(app, JwtBearerDefaults.AuthenticationScheme);
}
public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string authenticationScheme)
{
return app.Use(async (ctx, next) =>
{
if (ctx.User.Identity?.IsAuthenticated != true)
{
var result = await ctx.AuthenticateAsync(authenticationScheme);
if (result.Succeeded && result.Principal != null)
{
ctx.User = result.Principal;
}
}
await next();
});
}
}
}
Now you have now two different token type and two different validator. YOu can have API client using basic token info and JWT tokens are created by login from angular client. if you debug each request tries to pass two of them but just one of them succeed which is enough for you.
if aspnetboilerplate team updates sample according to this requirement it would be great.
来源:https://stackoverflow.com/questions/48243612/asp-net-boilerplate-identityserver