【问题标题】:IdentityServer4: HttpContext GetToken null but Token is delivered from serverIdentityServer4:HttpContext GetToken null 但令牌是从服务器传递的
【发布时间】:2019-09-16 08:59:13
【问题描述】:

我现在设法设置了一个 IdentityServer 应用程序和一个单独的 MVC 客户端应用程序,其中 IdentityServer 使用 MVC 应用程序的用户数据库。 MVC 应用用于用户注册、登录、注销和调用其他 API。

我可以登录到 MVC 应用程序,还可以从 IdentityServer 获取正确的令牌。但是现在我有两个问题:

  1. HttpContext 中的令牌为空,尽管 IdentityServer 成功发出令牌。但是当我尝试从 HttpContext 访问令牌时,以下两种变体都不起作用:
var accessToken = await HttpContext.GetTokenAsync("access_token");
var accessToken = await HttpContext.GetTokenAsync(IdentityConstants.ExternalScheme, "access_token");

可能是因为我在 VS2017 中使用“dotnet run”而不是使用 IIS 来启动两个项目?

  1. 在哪里或如何保存客户端的令牌以供将来调用 API?理想情况下,令牌会随每个请求自动发送。我忘记了这里的配置吗?

这是我的配置的样子:

IdentityServer 应用:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    var builder = services.AddIdentityServer(options =>
    {
        options.Events.RaiseErrorEvents = true;
        options.Events.RaiseInformationEvents = true;
        options.Events.RaiseFailureEvents = true;
        options.Events.RaiseSuccessEvents = true;
    })
        .AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryApiResources(Config.GetApis())
        .AddInMemoryClients(Config.GetClients())
        .AddAspNetIdentity<ApplicationUser>();

    if (Environment.IsDevelopment())
    {
        builder.AddDeveloperSigningCredential();
    }
    else
    {
        throw new Exception("need to configure key material");
    }
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseStaticFiles();
    app.UseIdentityServer();
    app.UseMvcWithDefaultRoute();
}

Config.cs

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
    };
}

public static IEnumerable<ApiResource> GetApis()
{
    return new List<ApiResource>
    {
        new ApiResource("api1", "My API")
    };
}

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "mvc",
            ClientName = "MVC Client",
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },

            RedirectUris           = { "http://localhost:5002/signin-oidc" },
            PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                "api1"
            },

            AllowOfflineAccess = true
        }
    };
}

MVC 客户端应用:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddDefaultIdentity<IdentityUser>()
        .AddDefaultUI(UIFramework.Bootstrap4)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";

            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;

            options.ClientId = "mvc";
            options.ClientSecret = "secret";
            options.ResponseType = "code id_token";

            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("api1");
            options.Scope.Add("offline_access");

            options.ClaimActions.MapJsonKey("website", "website");
        });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseAuthentication();

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseMvcWithDefaultRoute();
}

测试控制器

public async Task<IActionResult> LoginTest()
{
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
    }

    var tokenClient = new TokenClient(disco.TokenEndpoint, "mvc", "secret");
    var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("myusername", "mypassword");

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
    }

    Console.WriteLine(tokenResponse.Json);
    Console.WriteLine("\n\n");


    //var accessToken = await HttpContext.GetTokenAsync("access_token");
    var accessToken = await HttpContext.GetTokenAsync(IdentityConstants.ExternalScheme, "access_token");

    return View();
}

tokenResponse 已成功填充:

info: IdentityServer4.Events.DefaultEventService[0]
{
"Name": "Token Issued Success",
"Category": "Token",
"EventType": "Success",
"Id": 2000,
"ClientId": "mvc",
"ClientName": "MVC Client",
"Endpoint": "Token",
"SubjectId": "08be5c35-5593-49f4-999b-e5ddd694f2e9",
"Scopes": "openid profile api1 offline_access",
"GrantType": "password",
"Tokens": [
  {
    "TokenType": "refresh_token",
    "TokenValue": "****55a0"
  },
  {
    "TokenType": "access_token",
    "TokenValue": "****YuFw"
  }
],
"ActivityId": "0HLMAGC28PO61:00000001",
"TimeStamp": "2019-04-26T20:09:27Z",
"ProcessId": 21208,
"LocalIpAddress": "::1:5000",
"RemoteIpAddress": "::1"
}

我是 IdentityServer 的新手,到目前为止,我已经使用 WebAPI 开发了一个 SPA,并将 JWT 存储在本地存储中。我认为使用 MVC 客户端和 IdentityServer 有一种更舒适的方法。

【问题讨论】:

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


    【解决方案1】:

    当您向

    注册 OIDC 中间件时
    options.ResponseType = "code id_token";
    

    您的客户端应用程序中不需要任何登录控制器。中间件会将您的用户重定向到 IdentityServer 的登录页面,即 Authorize Endpoint。只需将Authorize 属性添加到任何安全控制器,您就应该在其中拥有:

    string accessToken = await HttpContext.GetTokenAsync("access_token");
    string idToken = await HttpContext.GetTokenAsync("id_token");
    

    您在示例 LoginTest() 方法中所做的是直接调用令牌端点,该端点不使用 OIDC 中间件,因此不会登录您的用户(未创建用户会话),而只是验证您提供并创建的凭据令牌。要使用所有协议功能,您还需要更改

    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    

    AllowedGrantTypes = GrantTypes.Hybrid, //or .Code
    

    就是这样

    【讨论】:

    • 感谢您的提示,但它仍然不起作用。我想问题是我拆分了身份服务器和 MVC 应用程序,并将用户数据放在 MVC 应用程序的数据库中。根据您的提示,我进一步走了一些步骤,但最后似乎身份服务器希望重定向到其自己的用户登录,该用户登录不存在以下 URL:https://localhost:5000/Account/Login?ReturnUrl=%...
    • 如果您按照我解释的方式(和默认方式)切换到 IdSrv 拥有的登录页面,是的。请查看官方快速入门以获取更多参考:github.com/IdentityServer/IdentityServer4.Quickstart.UI
    猜你喜欢
    • 2022-11-21
    • 1970-01-01
    • 2014-09-24
    • 2017-08-28
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 1970-01-01
    • 2012-04-10
    相关资源
    最近更新 更多