请求标头中的JWT在接收.Net Core API中是不同的 [英] JWT in Request Header is not the same in receiving .Net Core API

查看:78
本文介绍了请求标头中的JWT在接收.Net Core API中是不同的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我从Angular应用程序向.Net Core 2 API发出请求时,JWT与请求标头中发送的请求不同.

When I make a request to my .Net Core 2 API from my Angular app the JWT is not the same as the one sent in the request header.

Startup.cs

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        _config = builder.Build();
    }

    IConfigurationRoot _config;

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(_config);
        services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Transient);

        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();

        services.AddSingleton<IUserTwoFactorTokenProvider<ApplicationUser>, DataProtectorTokenProvider<ApplicationUser>>();

        // Add application services.

        // Add application repositories.

        // Add options.
        services.AddOptions();
        services.Configure<StorageAccountOptions>(_config.GetSection("StorageAccount"));

        // Add other.
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<ApiExceptionFilter>();

        // this makes "this.User" reflect the properties of the jwt sent in the request
        services.AddTransient<ClaimsPrincipal>(s => s.GetService<IHttpContextAccessor>().HttpContext.User);

        services.AddIdentity<ApplicationUser, IdentityRole>(options =>
        {
            // set password complexity requirements
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequireUppercase = false;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequiredLength = 6;

            options.Tokens.ProviderMap.Add("Default",
            new TokenProviderDescriptor(typeof(IUserTwoFactorTokenProvider<ApplicationUser>)));
        }).AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(config =>
            {
                config.RequireHttpsMetadata = false;
                config.SaveToken = true;
                config.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = _config["Tokens:Issuer"],
                    ValidAudience = _config["Tokens:Audience"],
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"])),
                    ValidateLifetime = true
                };
            });
        services.AddAuthorization(config =>
        {
            config.AddPolicy("Subscribers", p => p.RequireClaim("Subscriber", "True"));
            config.AddPolicy("Artists", p => p.RequireClaim("Artist", "True"));
            config.AddPolicy("Admins", p => p.RequireClaim("Admin", "True"));
        });

        services.Configure<DataProtectionTokenProviderOptions>(o =>
        {
            o.Name = "Default";
            o.TokenLifespan = TimeSpan.FromHours(1);
        });
        services.Configure<AuthMessageSenderOptions>(_config);

        // Add framework services.
        services.AddMvc(opt =>
        {
            //opt.Filters.Add(new RequireHttpsAttribute());
        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(_config.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.Use(async (context, next) =>
        {
            // just to check the context.User.Claims on request
            var temp = context;
            await next();
        });
        app.UseAuthentication();
        app.UseMvc();
    }
}

这是令牌的发布位置(在应用登录时)

This is where the token gets issued (on app login)

AuthController.cs

private async Task<IList<Claim>> CreateUserClaims(ApplicationUser user)
    {
        var userClaims = await _userManager.GetClaimsAsync(user);
        var newClaims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.NameId, user.Id)
        }.Union(userClaims).ToList();
        return newClaims;
    }
    private Object CreateToken(IList<Claim> claims)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _config["Tokens:Issuer"],
            audience: _config["Tokens:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddDays(29),
            signingCredentials: creds
        );
        return new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token),
            expiration = token.ValidTo
        };
    }
    private async Task<Object> CreateToken(ApplicationUser user)
    {
        var claims = await CreateUserClaims(user);
        var token = CreateToken(claims);
        return token;
    }
[HttpPost("token")]
    [AllowAnonymous]
    public async Task<IActionResult> CreateToken([FromBody] CredentialModel model)
    {
        var user = await _userManager.FindByNameAsync(model.UserName);
        if (user != null)
        {
            if (_hasher.VerifyHashedPassword(user, user.PasswordHash, model.Password)
                == PasswordVerificationResult.Success)
            {
                var token = await CreateToken(user);
                return Ok(token);
            }
        }
        throw new ApiException("Bad email or password.");
    }

我已通过Chrome调试器的网络"标签确认,我请求中的JWT是我希望该API获得的JWT.

I have confirmed through the Chrome debugger Network tab that the JWT in my request is the JWT I want the API to get.

因此,我将Angular请求代码从此帖子中删除.

这是一个通过UserId返回项目的控制器

Here is a Controller that returns items by UserId

[HttpGet]
    public async Task<IActionResult> Get()
    {
        var artists = await _manageArtistService.GetAllByUser(this.User);
        if (artists == null) return NotFound($"Artists could not be found");
        return Ok(artists);
    }

这是控制器调用的服务

public async Task<IEnumerable<ManageArtistView>> GetAllByUser(ClaimsPrincipal user)
    {
        // gets all artists of a given user, sorted by artist
        var userId = _userService.GetUserId(user);
        var artists = await _manageArtistRepository.GetAllByUser(userId);
        return artists;
    }

UserService.cs中,我尝试了几种不同的方法来访问当前用户.我检查了从控制器传递的this.User.

In the UserService.cs I have attempted a few different means of accessing the current user. I check the this.User that was passed from the Controller.

我还要检查_context中的当前上下文-您可以在Startup.cs中看到一个Singleton.

I also check the current context in _context - a Singleton you can see in the Startup.cs.

Startup.cs

services.AddTransient<ClaimsPrincipal>(s => s.GetService<IHttpContextAccessor>().HttpContext.User);

当我检查这些变量中的任何一个时,Claims对象包含与请求期间发送的JWT相同的声明.

When I inspect any of those variables, the Claims object does not contain the same claims as the JWT that was sent during the request.

我已通过检查 jwt.io 上的声明来验证声明不匹配. >

I have verified the claims do not match by checking the claims at jwt.io.

具体来说,我将给出一个方案:

To be specific, I'll give a scenario:

我使用电子邮件user@example.com登录到我的应用程序.然后将该电子邮件设置为AuthController.cs中的CreateUserClaims()函数内的user.UserName声明(子):

I sign into my app with email user@example.com. That email is then set as a claim (Sub) as user.UserName inside the CreateUserClaims() function in the AuthController.cs:

var newClaims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.NameId, user.Id)
        }.Union(userClaims).ToList();

然后设置其他一些属性,最终将令牌返回给客户端.客户端将其存储在localStorage中.

Then some other properties are set and eventually the token is returned to the client. The client stores it in localStorage.

然后,客户端发出一个请求,包括标头中的JWT并将其添加到这样的请求选项中(Angular服务):

The client then makes a request, including the JWT in the header and adds it to the request options like this (Angular service):

private headers = new Headers(
    {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + this.authService.token
    });
private options = new RequestOptions({ headers: this.headers });

我检查网络"标签中的标头,其中包含JWT-我在 jwt.io 上对其进行了检查看起来不错-具有正确的电子邮件和其他声明.

I check the Header in the Network tab and it contains the JWT - I check it on jwt.io and it looks good - has the proper email and other claims.

现在,我可以注销该应用程序,以其他用户身份登录,获取一个新的JWT,并向上面显示的同一控制器发出请求,JWT将收到以前的电子邮件,而不是我刚刚登录的新帐户.

Now I can logout of the app, sign in as a different user, get a new JWT, and make the request to that same controller shown above and the JWT will have the previous email, not the new one that I just signed in as.

我确实进行了相同的检查,检查了网络"选项卡上标题"中的JWT,以确保声明包含新的电子邮件,如sub以及其他声明.

And I did go through the same checks, checking the JWT in the Header on the Network tab to ensure the claims contain the new email as the sub as well as the other claims.

因此,这意味着我在新登录时获得了正确的JWT,但API仍在寻找旧的JWT.

So that means I was issued the proper JWT on the new sign in, but somehow the API is still looking at the old JWT.

那有多疯狂?

我注意到的另一件事是,即使是在第一次登录时(假装我只是用dotnet run启动了API,然后我向与上述相同的控制器发出了第一个请求,它将丢失nameid声明.我可以检查Header请求中发送的JWT,它拥有nameid声明.因此,再次,该api将发出正确的JWT,但是当我将其发送回去时请求中的HTTP API与我在请求中发送的JWT不同.

Something else I have noticed is that even on that first login (pretend I just started the API with dotnet run and then I make my first request to the same controller shown above it will be missing the nameid claim. I can go check the JWT that was sent in the Header request and it does have the nameid claim. So, again, the api will issue the proper JWT but when I send it back over HTTP in a request the API does not have the same JWT that I sent in the request.

一件事 为了简单起见,我在控制台中记录了JWT.我回去,发现今天上午9点开始使用的第一个.它的jti与.net核心API中当前的jti相同.现在是下午4:45.在这两次之间(上午9点和下午4:45点),我的控制台中有9个不同的JTW,它们都是通过API发出的.但是该API似乎保留了今天早上创建的第一个API-即使在我多次停止并启动该项目之后.

ONE MORE THING I log the JWT in the console for simplicity. I went back and found the first one I started using today, at 9am. Its jti is the same as the one that is currently in the .net core API. It's now 4:45pm. I have 9 different JTWs in my console between those two times (9am and 4:45pm), all issued from the API. But the API seems to have kept the first one it created this morning - even after I have stopped and started the project numerous times.

请帮助我了解我在做什么错.我一定不能完全理解JWT的处理方式.

Please help me understand what I am doing wrong. I must not be fully understanding how JWTs are handled.

推荐答案

我已经解决了部分问题.

I have figured out part of my problem.

我说来自UI的令牌与.net API接收的令牌不同是错误的.我说我正在检查网络"选项卡中的标题",但确实是,但不是正确的请求.我的UI正在发送多个请求-来自不同的Angular模块.我在每个模块中注入了新的身份验证服务(存储我的令牌).注销时,从未刷新过模块,因此那些未保留其令牌的旧副本的模块.因此,在登录时,只有受影响的模块(在我的情况下是我的主app.module.ts)正在刷新.未被触摸的用户保留其身份验证服务的相同副本.

I was wrong in saying the token coming from the UI was different than what the .net API was receiving. I said I was inspecting the Header in the Network tab, and I was, but just not the correct request. My UI was sending several requests - from different Angular modules. I was injecting a new authentication service (where my token is stored) in each module. On logout, not ever module was getting refreshed, so those that were not kept their old copy of the token. Therefore, upon login, only the affected modules (in my case, my main app.module.ts) were getting refreshed. The ones that had not been touched kept their same copy of the authentication service.

我从每个模块中删除了注入,并让它们从主模块中继承.app.module.ts解决了UI和API似乎具有不同令牌的问题.

I removed the injection from each module and let them inherit from the main app.module.ts That fixed the issue of the UI and API appearing to have different tokens.

我提到的另一个无法看到nameid声明的问题已部分解决.我在User中总共有10个Claims.当我解码JWT时,它说我有subnameid.但是,当我检查UserService.cs中的Claims时,它们并未列为nameidsub,而是列为http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier.每个都有正确的Value.我不确定这在哪里或如何发生.我创建了以下自定义中间件代码,以查看令牌进入时的含义,并且令牌中的Claimsubnameid.

The other issue I mentioned of not being able to see the nameid claim is partially resolved. I have a total of 10 Claims inside User. When I decode the JWT it says I have sub and nameid. However, when I inspect Claims in my UserService.cs they are not listed as nameid and sub, but rather http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier and http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier. Each have the correct Value. I am not sure where or how this happens. I created the following custom middleware code to see what the token was when coming in and it has the Claim as sub and nameid.

app.Use(async (context, next) =>
        {
            var authHeader = context.Request.Headers["Authorization"].ToString();
            if (authHeader != null && authHeader.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
            {
                var tokenStr = authHeader.Substring("Bearer ".Length).Trim();
                System.Console.WriteLine(tokenStr);
                var handler = new JwtSecurityTokenHandler();
                var token = handler.ReadToken(tokenStr) as JwtSecurityToken;
                var nameid = token.Claims.First(claim => claim.Type == "nameid").Value;

                var identity = new ClaimsIdentity(token.Claims);
                context.User = new ClaimsPrincipal(identity);
            }
            await next();
        });

因此,变量nameid是正确的,并且包含期望值.沿线的某个位置,Typenameidsub更改为http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

So, the variable nameid is right and contains the expected value. Somewhere along the line the Type is changing from nameid and sub to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

这篇关于请求标头中的JWT在接收.Net Core API中是不同的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆