如何使用 openid 连接混合流来代表用户调用 Api (IdentityServer4 Asp.Net Core 2.0)?
How to us openid connect hybrid flow to call an Api on behalf of user (IdentityServer4 Asp.Net Core 2.0)?
我正在尝试使用 IdentityServer4 让 Web 应用程序代表用户调用 API。我希望 API 将具有用户的用户身份信息,即使它是发出请求的 Web 应用程序。
一切正常(Authentication/Claims 等)除了 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
尝试根据 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";
});
当然要用你的权限修改它url。其他的似乎是合法的。
PS:AddIdentityServerAuthentication
随 IdentityServer4.AccessTokenValidation
软件包一起提供
编辑
根据评论 - 删除此行:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
让我们看看会发生什么。
编辑 2
经过一些讨论,我们发现 Zzz 正在使用一些 Pluralsight 教程来实现 asp.net 身份。对于那些正在阅读本文并开始实施 IdentityServer 作为其应用程序身份验证的人 - 关注官方 documentation and also check the samples.
PS:并且始终启用日志记录。它就是一切,可以节省大量时间。
我正在尝试使用 IdentityServer4 让 Web 应用程序代表用户调用 API。我希望 API 将具有用户的用户身份信息,即使它是发出请求的 Web 应用程序。
一切正常(Authentication/Claims 等)除了 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
尝试根据 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";
});
当然要用你的权限修改它url。其他的似乎是合法的。
PS:AddIdentityServerAuthentication
随 IdentityServer4.AccessTokenValidation
软件包一起提供
编辑
根据评论 - 删除此行:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
让我们看看会发生什么。
编辑 2
经过一些讨论,我们发现 Zzz 正在使用一些 Pluralsight 教程来实现 asp.net 身份。对于那些正在阅读本文并开始实施 IdentityServer 作为其应用程序身份验证的人 - 关注官方 documentation and also check the samples.
PS:并且始终启用日志记录。它就是一切,可以节省大量时间。