简体   繁体   English

如何在asp.net核心中从JWT检索ClaimsPrincipal

[英]How to retrieve ClaimsPrincipal from JWT in asp.net core

In my solution, I have two projects.在我的解决方案中,我有两个项目。 1) Web API and 2) MVC. 1) Web API 和 2) MVC。 I am using ASP.NET Core.我正在使用 ASP.NET 核心。 API issues JWT token and MVC consumes it to get protected resources. API 发布 JWT 令牌,MVC 使用它来获取受保护的资源。 I am using openiddict library to issue JWT.我正在使用openiddict库来发布 JWT。 In MVC project, in AccountController Login method, I want to retrieve ClaimsPrincipal (using JwtSecurityTokenHandler ValidateToken method) and assign to HttpContext.User.Claims and HttpContext.User.Identity.在 MVC 项目中,在 AccountController Login 方法中,我想检索 ClaimsPrincipal(使用 JwtSecurityTokenHandler ValidateToken 方法)并分配给 HttpContext.User.Claims 和 HttpContext.User.Identity。 I want to store the token in session and for each request after successful login, pass it in header to Web API.我想将令牌存储在会话中,并且在成功登录后对于每个请求,将其在标头中传递给 Web API。 I can successfully, issue JWT and consume it in MVC project, but when I try to retrieve ClaimsPrincipal it throws me an error.我可以成功地发布 JWT 并在 MVC 项目中使用它,但是当我尝试检索 ClaimsPrincipal 时它会抛出一个错误。 First of all, I am not even sure whether this retrieving of ClaimsPrinciapal from JWT is a right approach or not.首先,我什至不确定从 JWT 检索 ClaimsPrinciapal 是否是正确的方法。 And if it is, what is the way forward.如果是,那么前进的道路是什么。

WebAPI.Startup.CS WebAPI.Startup.CS

public class Startup
{
    public static string SecretKey => "MySecretKey";
    public static SymmetricSecurityKey SigningKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));

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

    public IContainer ApplicationContainer { get; private set; }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddCors();
        services.AddMvc().AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); });
        services.AddAutoMapper();

        services.AddDbContext<MyDbContext>(options =>
        {
            options.UseMySql(Configuration.GetConnectionString("MyDbContext"));
            options.UseOpenIddict();
        });

        services.AddOpenIddict(options =>
        {
            options.AddEntityFrameworkCoreStores<TelescopeContext>();
            options.AddMvcBinders();
            options.EnableTokenEndpoint("/Authorization/Token");
            options.AllowPasswordFlow();
            options.AllowRefreshTokenFlow();
            options.DisableHttpsRequirement();
            options.UseJsonWebTokens();
            options.AddEphemeralSigningKey();
            options.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
        });

        var config = new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile()); });
        services.AddSingleton(sp => config.CreateMapper());

        // Create the Autofac container builder.
        var builder = new ContainerBuilder();

        // Add any Autofac modules or registrations.
        builder.RegisterModule(new AutofacModule());

        // Populate the services.
        builder.Populate(services);

        // Build the container.
        var container = builder.Build();

        // Create and return the service provider.
        return container.Resolve<IServiceProvider>();
    }

    // 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, IApplicationLifetime applicationLifetime)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseCors(builder => builder.WithOrigins("http://localhost:9001/")
                                      .AllowAnyOrigin());

        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            Authority = "http://localhost:9001/",
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            Audience = "http://localhost:9000/",
            RequireHttpsMetadata = false,
            TokenValidationParameters = new TokenValidationParameters()
            {
                ValidateIssuer = true,
                ValidIssuer = "http://localhost:9001/",

                ValidateAudience = true,
                ValidAudience = "http://localhost:9000",

                ValidateLifetime = true,
                IssuerSigningKey = SigningKey
            }
        });

        app.UseOpenIddict();
        app.UseMvcWithDefaultRoute();
        applicationLifetime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
    }
}

WebAPI.AuthorizationController.cs which issues JWT.发布 JWT 的 WebAPI.AuthorizationController.cs。

[Route("[controller]")]
public class AuthorizationController : Controller
{
    private IUsersService UserService { get; set; }

    public AuthorizationController(IUsersService userService)
    {
        UserService = userService;
    }

    [HttpPost("Token"), Produces("application/json")]
    public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
    {
        if (request.IsPasswordGrantType())
        {
            if (await UserService.AuthenticateUserAsync(new ViewModels.AuthenticateUserVm() { UserName = request.Username, Password = request.Password }) == false)
                return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);

            var user = await UserService.FindByNameAsync(request.Username);

            var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, null);
            identity.AddClaim(OpenIdConnectConstants.Claims.Subject, user.UserId.ToString(), OpenIdConnectConstants.Destinations.AccessToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.Username, user.UserName, OpenIdConnectConstants.Destinations.AccessToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.Email, user.EmailAddress, OpenIdConnectConstants.Destinations.IdentityToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.GivenName, user.FirstName, OpenIdConnectConstants.Destinations.IdentityToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.MiddleName, user.MiddleName, OpenIdConnectConstants.Destinations.IdentityToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.FamilyName, user.LastName, OpenIdConnectConstants.Destinations.IdentityToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.EmailVerified, user.IsEmailConfirmed.ToString(), OpenIdConnectConstants.Destinations.IdentityToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.Audience, "http://localhost:9000", OpenIdConnectConstants.Destinations.AccessToken);

            var principal = new ClaimsPrincipal(identity);

            return SignIn(principal, OpenIdConnectServerDefaults.AuthenticationScheme);
        }

        throw new InvalidOperationException("The specified grant type is not supported.");
    }
}

MVC.AccountController.cs contains Login, GetTokenAsync method. MVC.AccountController.cs 包含 Login、GetTokenAsync 方法。

public class AccountController : Controller
    {
        public static string SecretKey => "MySecretKey";
        public static SymmetricSecurityKey SigningKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));

[HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginVm vm, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            if (ModelState.IsValid)
            {
                var token = await GetTokenAsync(vm);

                SecurityToken validatedToken = null;

                TokenValidationParameters validationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidIssuer = "http://localhost:9001/",

                    ValidateAudience = true,
                    ValidAudience = "http://localhost:9000",

                    ValidateLifetime = true,
                    IssuerSigningKey = SigningKey
                };

                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();

                try
                {
                    ClaimsPrincipal principal = handler.ValidateToken(token.AccessToken, validationParameters, out validatedToken);
                }
                catch (Exception e)
                {
                    throw;
                }
            }

            return View(vm);
        }

        private async Task<TokenVm> GetTokenAsync(LoginVm vm)
        {
            using (var client = new HttpClient())
            {
                var request = new HttpRequestMessage(HttpMethod.Post, $"http://localhost:9001/Authorization/Token");
                request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
                {
                    ["grant_type"] = "password",
                    ["username"] = vm.EmailAddress,
                    ["password"] = vm.Password
                });

                var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
                response.EnsureSuccessStatusCode();

                var payload = await response.Content.ReadAsStringAsync();
                //if (payload["error"] != null)
                //    throw new Exception("An error occurred while retriving an access tocken.");                

                return JsonConvert.DeserializeObject<TokenVm>(payload);
            }
        }
}

Error I am getting: "IDX10501: Signature validation failed. Unable to match 'kid': '0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB', \\ntoken: '{\\"alg\\":\\"RS256\\",\\"typ\\":\\"JWT\\",\\"kid\\":\\"0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB\\"}.{\\"sub\\":\\"10\\",\\"username\\":\\"...我收到的错误:“IDX10501:签名验证失败。无法匹配‘孩子’:‘0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB’,\\ntoken:‘{\\"alg\\":\\"RS256\\",\\"typ\\ ":\\"JWT\\",\\"kid\\":\\"0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB\\"}.{\\"sub\\":\\"10\\",\\"用户名\\":\\".. .

See this thread because I was looking for the very same thing (not the exception though), and the accepted answer indeed helps the OP, however, it doesn't help me with : how to create ClaimsPrincipal from JWT Token.看到这个线程,因为我正在寻找完全相同的东西(虽然不是例外),并且接受的答案确实有助于 OP,但是,它对我没有帮助:如何从 JWT 令牌创建ClaimsPrincipal

After some research and digging, I've found a way to do it manually (it was my case, I had to do it manually in a specific case).经过一些研究和挖掘,我找到了一种手动完成的方法(这是我的情况,我必须在特定情况下手动完成)。

To do so, first, parse the token with JwtSecurityTokenHandler class :为此,首先,使用JwtSecurityTokenHandler类解析令牌:

var token = new JwtSecurityTokenHandler().ReadJwtToken(n.TokenEndpointResponse.AccessToken);

After that, you just ned to create a new ClaimsPrincipal :之后,您只需创建一个新的 ClaimsPrincipal :

var identity = new ClaimsPrincipal(new ClaimsIdentity(token.Claims));

In my specific case, I just have to update claims on my already authenticated user, so I use this code :在我的特定情况下,我只需要更新我已经通过身份验证的用户的声明,所以我使用以下代码:

var identity = (ClaimsIdentity)User.Identity;
identity.AddClaims(token.Claims);

Hope it will help someone one day if looking after the answer for the title.希望有一天,如果照顾标题的答案,它会帮助某人。

First of all, I am not even sure whether this retrieving of ClaimsPrinciapal from JWT is a right approach or not.首先,我什至不确定从 JWT 检索 ClaimsPrinciapal 是否是正确的方法。

It's likely not the approach I'd personally use.这可能不是我个人使用的方法。 Instead, I'd simply rely on the JWT middleware to extract the ClaimsPrincipal from the access token for me (no need to manually use JwtSecurityTokenHandler for that).相反,我只是依靠 JWT 中间件为我从访问令牌中提取ClaimsPrincipal (无需为此手动使用JwtSecurityTokenHandler )。

The exception thrown by IdentityModel is actually caused by a very simple root cause: you've configured OpenIddict to use an ephemeral RSA asymmetric signing key (via AddEphemeralSigningKey()) and registered a symmetric signing key in the JWT bearer options, a scenario that can't obviously work. IdentityModel 抛出的异常实际上是由一个非常简单的根本原因引起的:您已将 OpenIddict 配置为使用临时 RSA 非对称签名密钥(通过AddEphemeralSigningKey())并在 JWT 承载选项中注册了一个对称签名密钥,该场景可以显然不行。

You have two options to fix that:你有两个选择来解决这个问题:

  • Register your symmetric signing key in the OpenIddict options using AddSigningKey(SigningKey) so OpenIddict can use it.使用AddSigningKey(SigningKey)在 OpenIddict 选项中注册您的对称签名密钥,以便 OpenIddict 可以使用它。

  • Use an asymmetric signing key (by calling AddEphemeralSigningKey() , or AddSigningCertificate() / AddSigningKey() on production) and let the JWT bearer middleware use it instead of your symmetric signing key.使用非对称签名密钥(通过在生产中调用AddEphemeralSigningKey()AddSigningCertificate() / AddSigningKey() )并让 JWT 不记名中间件使用它而不是您的对称签名密钥。 For that, remove the entire TokenValidationParameters configuration to allow IdentityModel to download the public signing key from OpenIddict's discovery endpoint.为此,删除整个TokenValidationParameters配置以允许 IdentityModel 从 OpenIddict 的发现端点下载公共签名密钥。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM