【问题标题】:How to us openid connect hybrid flow to call an Api on behalf of user (IdentityServer4 Asp.Net Core 2.0)?我们如何 openid 连接混合流以代表用户调用 Api(IdentityServer4 Asp.Net Core 2.0)?
【发布时间】:2018-08-03 23:31:50
【问题描述】:

我正在尝试使用 IdentityServer4 让 Web 应用程序代表用户调用 API。我希望 API 将拥有用户的用户身份信息,即使它是发出请求的 Web 应用程序。

除了在 API 中 User.Identity.Name 为空外,一切都正常工作(身份验证/声明等)。 Web 应用程序中的 User.Identity.Name 返回正确的用户名。下面是我尝试过的一个例子。

我正在使用 IdentityServer4 v- 2.1.2 身份模型 v- 3.1.1 Microsoft.AspNetCore.All v- 2.0.5

正如一些背景知识:我正在学习 PluralSight 教程 - https://app.pluralsight.com/library/courses/aspnet-core-identity-management-playbook/table-of-contents

API 控制器

[Produces("application/json")]
[Route("api")]
public class ApiController : Controller
{
    [Route("user")]
    [Authorize]
    public IActionResult GetUser()
    {
        return Content("User " + User.Identity.Name);
    }
}

API Startup.cs - ConfigureServices 方法

services.AddAuthentication()
    .AddJwtBearer(options => 
    {
        options.Authority = "https://localhost:44335";
        options.Audience = "DemoApi";
        options.TokenValidationParameters.NameClaimType = "name";
    });

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();
});

IdentityServer InMemory 客户端设置

new Client()
{
    ClientId = "WebApp",
    AllowedGrantTypes = GrantTypes.Hybrid,
    ClientSecrets = new [] {new Secret("MySecret".Sha256())},
    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "DemoApi"
    },
    RedirectUris = { "https://localhost:44343/signin-oidc" },
    PostLogoutRedirectUris = new List<string>
    {
        "https://localhost:44343/signout-callback-oidc"
    },
    AllowOfflineAccess = true,
    RequireConsent = false
}

IdentityServer 测试用户设置:

new TestUser()
{
    SubjectId = "1",
    Username = "testname",
    Password = "pass123",
    Claims = new []
    {
        new Claim("name", "testname")
    }
}

Web 应用程序 startup.cs ConfigureServices 方法 - 身份验证设置

services.AddAuthentication(options => {
    options.DefaultChallengeScheme = "oidc";
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = "oidc";
}).AddOpenIdConnect("oidc", options => {
    options.Authority = "https://localhost:44335/";
    options.ClientId = "WebApp";
    options.ClientSecret = "MySecret";
    options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
    options.Scope.Add("DemoApi");
    options.Scope.Add("offline_access");
    options.SignedOutRedirectUri = "/";
    options.TokenValidationParameters.NameClaimType = "name";
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
}).AddCookie();

Web 应用程序 API 调用 -

[Route("callapi")]
[Authorize]
public async Task<IActionResult> CallApi()
{
    string accessToken;
    try
    {
        accessToken = await GetAccessToken();
    }
    catch (System.Exception ex)
    {
        ViewBag.Error = ex.GetBaseException().Message;
        return View();
    }

    var client = new HttpClient();
    client.SetBearerToken(accessToken);
    try
    {
        var content = await client.GetStringAsync("https://localhost:44379/api/user");
        ViewBag.ApiResponse = content;
    }
    catch (Exception ex)
    {
        ViewBag.ApiResponse = ex.GetBaseException().Message;                
    }

    ViewBag.AccessToken = accessToken;
    ViewBag.RefreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);

    return View();
}

private async Task<string> GetAccessToken()
{
    var exp = await HttpContext.GetTokenAsync("expires_at");
    var expires = DateTime.Parse(exp);

    if (expires > DateTime.Now)
    {
        return await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
    }

    return await GetRefreshedAccessToken();
}

private async Task<string> GetRefreshedAccessToken()
{
    var disco = await DiscoveryClient.GetAsync("https://localhost:44335/");
    var tokenClient = new TokenClient(disco.TokenEndpoint, "WebApp", "MySecret");
    var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
    var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken);

    if (tokenResponse.IsError)
    {
        var auth = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        auth.Properties.UpdateTokenValue(OpenIdConnectParameterNames.AccessToken, tokenResponse.AccessToken);
        auth.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, tokenResponse.RefreshToken);
        var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
        auth.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, auth.Principal, auth.Properties);
        return tokenResponse.AccessToken;
    }

    throw tokenResponse.Exception;
}

API - User.Identity

【问题讨论】:

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


    【解决方案1】:

    尝试根据official documentation 设置您的 API。

    据我所知,不同之处在于身份验证类型。你有

    services.AddAuthentication()
                .AddJwtBearer(options => 
                {
                    options.Authority = "https://localhost:44335";
                    options.Audience = "DemoApi";
                    options.TokenValidationParameters.NameClaimType = "name";
                });
    

    虽然文档说:

    services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
    
                options.ApiName = "api1";
            });
    

    当然可以用你的授权网址修改它。其他的事情似乎是合法的。

    PS:AddIdentityServerAuthenticationIdentityServer4.AccessTokenValidation 包一起提供

    编辑

    基于 cmets - 删除此行:

    services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
                    .RequireAuthenticatedUser()
                    .Build();
            });
    

    让我们看看会发生什么。

    编辑 2

    经过一些讨论,我们发现 Zzz 正在使用 Pluralsight 教程来获取 asp.net 身份。对于那些正在阅读本文并开始实施 IdentityServer 作为其应用程序身份验证的人 - 请关注官方 documentation 并查看 samples

    PS:并且始终启用日志记录。它就是一切,可以节省大量时间。

    【讨论】:

    • 我尝试了你的建议。 API 方法中的 User.Identity.Name 仍然为 null。
    • 索赔等等。你有吗?该对象是声明身份吗?
    • 我在 API 中询问。在 API 方法中,User.Identity 对象 - 它是什么类型,如果是声明身份,您是否看到那里的声明?
    • 稍微更新了答案
    猜你喜欢
    • 2020-07-06
    • 1970-01-01
    • 2018-09-29
    • 2018-03-06
    • 2018-04-17
    • 2019-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多