ASP.NET Boilerplate + IdentityServer

霸气de小男生 提交于 2020-08-22 06:25:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!