【问题标题】:How to retrieve ClaimsPrincipal from JWT in asp.net core如何在 asp.net core 中从 JWT 检索 ClaimsPrincipal
【发布时间】:2017-08-24 23:46:11
【问题描述】:

在我的解决方案中,我有两个项目。 1) Web API 和 2) MVC。我正在使用 ASP.NET 核心。 API 发出 JWT 令牌,MVC 使用它来获取受保护的资源。我正在使用 openiddict 库来发布 JWT。在 MVC 项目中,在 AccountController Login 方法中,我想检索 ClaimsPrincipal(使用 JwtSecurityTokenHandler ValidateToken 方法)并分配给 HttpContext.User.Claims 和 HttpContext.User.Identity。我想将令牌存储在会话中,并且对于成功登录后的每个请求,将其在标头中传递给 Web API。我可以成功地发出 JWT 并在 MVC 项目中使用它,但是当我尝试检索 ClaimsPrincipal 时,它会抛出一个错误。首先,我什至不确定从 JWT 检索 ClaimsPrinciapal 是否正确。如果是的话,前进的方向是什么。

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());
    }
}

发出 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 包含 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);
            }
        }
}

我收到的错误:“IDX10501:签名验证失败。无法匹配 'kid':'0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB',\ntoken:'{\"alg\":\"RS256\",\ "typ\":\"JWT\",\"kid\":\"0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB\"}.{\"sub\":\"10\",\"用户名\":\ “...

【问题讨论】:

    标签: c# asp.net-core asp.net-core-mvc openiddict


    【解决方案1】:

    看到这个帖子,因为我正在寻找同样的东西(虽然不是例外),并且接受的答案确实有助于 OP,但是,它对我没有帮助:如何从 JWT Token 创建ClaimsPrincipal .

    经过一番研究和挖掘,我找到了一种手动完成的方法(这是我的情况,我必须在特定情况下手动完成)。

    为此,首先,使用JwtSecurityTokenHandler 类解析令牌:

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

    之后,您只需创建一个新的 ClaimsPrincipal :

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

    在我的具体情况下,我只需要更新我已经通过身份验证的用户的声明,所以我使用此代码:

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

    希望有一天能对标题的答案有所帮助。

    【讨论】:

    • 这就是为什么我来到这里,你的帖子解决了我的问题。不确定声明是否是 jwt 的最佳选择。
    • 这正是我在实施自定义 JWT Refresh Token Rotation 解决方案时需要做的。谢谢。
    【解决方案2】:

    首先,我什至不确定从 JWT 检索 ClaimsPrinciapal 是否正确。

    这可能不是我个人使用的方法。相反,我只是依靠 JWT 中间件为我从访问令牌中提取 ClaimsPrincipal(无需为此手动使用 JwtSecurityTokenHandler)。

    IdentityModel 抛出的异常实际上是由一个非常简单的根本原因引起的:您已将 OpenIddict 配置为使用临时 RSA 非对称签名密钥(通过 AddEphemeralSigningKey()) 并在JWT 不记名选项,这种情况显然行不通。

    你有两个选项来解决这个问题:

    • 使用 AddSigningKey(SigningKey) 在 OpenIddict 选项中注册您的对称签名密钥,以便 OpenIddict 可以使用它。

    • 使用非对称签名密钥(通过调用 AddEphemeralSigningKey()AddSigningCertificate()/AddSigningKey() 在生产环境中)并让 JWT 不记名中间件使用它而不是您的对称签名密钥。为此,请删除整个 TokenValidationParameters 配置,以允许 IdentityModel 从 OpenIddict 的发现端点下载公共签名密钥。

    【讨论】:

    • 感谢@PinPoint。第一个选项奏效了。也将尝试第二个选项,但截至目前,继续第一个。
    • 很抱歉投了反对票。我一定是无意中想要投票的,现在除非编辑帖子,否则我无法更改它。
    猜你喜欢
    • 2019-08-16
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-24
    • 1970-01-01
    • 2017-09-27
    相关资源
    最近更新 更多