检索 UserInfo 的访问令牌
Retrieving access token for UserInfo
我希望能够访问 UserInfo 端点 /connect/userinfo
但是当我这样做时它显示 401... 暗示需要身份验证。所以我 运行 进入这篇文章,讨论我们如何在 .net 代码中实现它以便能够访问 UserInfo Endpoint。因此,在我的 TokenServices.cs
文件中,我执行了以下操作,但我不确定如何获取令牌本身。 我确实有一个方法 GetToken()
可以检索令牌,但我不确定我是否可以将该方法用于设置 Token = token
.[=32 的行=]
public async Task<TokenResponse> GetUserInfoToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = _discoveryDocument.UserInfoEndpoint,
// Token = token
});
if (tokenResponse.isError)
{
_logger.LogError($"Unable to get userinfo token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get UserInfo token", tokenResponse.Exception);
}
return tokenResponse;
}
这是完整的 class 文件:
namespace WeatherMVC.Services
{
public class TokenService : ITokenService
{
private readonly ILogger<TokenService> _logger;
private readonly IOptions<IdentityServerSettings> _identityServerSettings;
private readonly DiscoveryDocumentResponse _discoveryDocument;
public TokenService(ILogger<TokenService> logger, IOptions<IdentityServerSettings> identityServerSettings)
{
_logger = logger;
_identityServerSettings = identityServerSettings;
using var httpClient = new HttpClient();
_discoveryDocument = httpClient.GetDiscoveryDocumentAsync(identityServerSettings.Value.DiscoveryUrl).Result;
if (_discoveryDocument.IsError)
{
logger.LogError($"Unable to get discovery document. Error is: {_discoveryDocument.Error}");
throw new Exception("Unable to get discovery document", _discoveryDocument.Exception);
}
}
public async Task<TokenResponse> GetToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = _discoveryDocument.TokenEndpoint,
ClientId = _identityServerSettings.Value.ClientName,
ClientSecret = _identityServerSettings.Value.ClientPassword,
Scope = scope
});
if (tokenResponse.IsError)
{
_logger.LogError($"Unable to get token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get token", tokenResponse.Exception);
}
return tokenResponse;
}
public async Task<TokenResponse> GetUserInfoToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = _discoveryDocument.UserInfoEndpoint,
// Token = token
});
if (tokenResponse.isError)
{
_logger.LogError($"Unable to get userinfo token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get UserInfo token", tokenResponse.Exception);
}
return tokenResponse;
}
}
在我的HomeController
里面我有:
namespace WeatherMVC.Controllers
{
public class HomeController : Controller
{
private readonly ITokenService _tokenService;
private readonly ILogger<HomeController> _logger;
public HomeController(ITokenService tokenService, ILogger<HomeController> logger)
{
_tokenService = tokenService;
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[Authorize] // 25:44 in youtube video
public async Task<IActionResult> Weather()
{
var data = new List<WeatherData>();
using (var client = new HttpClient())
{
var tokenResponse = await _tokenService.GetToken("weatherapi.read");
client.SetBearerToken(tokenResponse.AccessToken);
var result = client
.GetAsync("https://localhost:5445/weatherforecast")
.Result;
if (result.IsSuccessStatusCode)
{
var model = result.Content.ReadAsStringAsync().Result;
data = JsonConvert.DeserializeObject<List<WeatherData>>(model);
return View(data);
}
else
{
throw new Exception("Unable to get content");
}
}
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration["InteractiveServiceSettings:AuthorityUrl"];
options.ClientId = Configuration["InteractiveServiceSettings:ClientId"];
options.ClientSecret = Configuration["InteractiveServiceSettings:ClientSecret"];
options.ResponseType = "code";
options.UsePkce = true;
options.ResponseMode = "query";
options.Scope.Add(Configuration["InteractiveServiceSettings:Scopes:0"]);
options.SaveTokens = true;
});
services.Configure<IdentityServerSettings>(Configuration.GetSection("IdentityServerSettings"));
services.AddSingleton<ITokenService, TokenService>();
}
WeatherApi Project
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication("Bearer", options =>
{
options.ApiName = "weatherapi";
options.Authority = "https://localhost:5443";
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "weatherapi", Version = "v1" });
});
}
Identity project
namespace identity
{
public static class Config
{
public static List<TestUser> Users
{
get
{
var address = new
{
street_address = "One Hacker Way",
locality = "Heidelberg",
postal_code = 69118,
country = "Germany"
};
return new List<TestUser>
{
new TestUser
{
SubjectId = "818727",
Username = "alice",
Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "admin"),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address),
IdentityServerConstants.ClaimValueTypes.Json)
}
},
new TestUser
{
SubjectId = "88421113",
Username = "bob",
Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "user"),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address),
IdentityServerConstants.ClaimValueTypes.Json)
}
}
};
}
}
public static IEnumerable<IdentityResource> IdentityResources =>
new[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new[]
{
new ApiScope("weatherapi.read"),
new ApiScope("weatherapi.write"),
};
public static IEnumerable<ApiResource> ApiResources => new[]
{
new ApiResource("weatherapi")
{
Scopes = new List<string> {"weatherapi.read", "weatherapi.write"},
ApiSecrets = new List<Secret> {new Secret("ScopeSecret".Sha256())},
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<Client> Clients =>
new[]
{
// m2m client credentials flow client
new Client
{
ClientId = "m2m.client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedScopes = {"weatherapi.read", "weatherapi.write"}
},
// interactive client using code flow + pkce
new Client
{
ClientId = "interactive",
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {"https://localhost:5444/signin-oidc" , "https://localhost:44394/signin-oidc"},
FrontChannelLogoutUri = "https://localhost:5444/signout-oidc",
PostLogoutRedirectUris = {"https://localhost:5444/signout-callback-oidc"},
AllowOfflineAccess = true,
AllowedScopes = {"openid", "profile", "weatherapi.read"},
RequirePkce = true,
RequireConsent = true,
AllowPlainTextPkce = false
},
};
}
}
是否有特定的方法可以调用令牌以便将令牌设置为该 UserInfo 令牌?任何 pointers/suggestions 将不胜感激!
已更新
要访问此屏幕(HomeController -> Weather()
),我必须获得授权,当我访问此页面时,无论不记名令牌持续多长时间,它都会让我保持登录状态。那么,为什么我无法访问 /connect/userinfo
页面?
使用options.SaveTokens = true; (在 AddOpenIDConnect 中)会将所有令牌保存在用户 cookie 中,然后您可以使用以下方式访问它:
var accessToken = await HttpContext.GetTokenAsync("access_token");
Sample code to get all the tokens if provided:
ViewBag.access_token = HttpContext.GetTokenAsync("access_token").Result;
ViewBag.id_token = HttpContext.GetTokenAsync("id_token").Result;
ViewBag.refresh_token = HttpContext.GetTokenAsync("refresh_token").Result;
ViewBag.token_type = HttpContext.GetTokenAsync("token_type").Result; //Bearer
ViewBag.expires_at = HttpContext.GetTokenAsync("expires_at").Result; // "2021-02-01T10:58:28.0000000+00:00"
发出请求的示例代码
var accessToken = await HttpContext.GetTokenAsync("access_token");
var authheader = new AuthenticationHeaderValue("Bearer", accessToken);
var client = new HttpClient();
var authheader = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Authorization = authheader;
var content = await client.GetStringAsync("https://localhost:7001/api/payment");
ViewBag.Json = JObject.Parse(content).ToString();
return View();
我希望能够访问 UserInfo 端点 /connect/userinfo
但是当我这样做时它显示 401... 暗示需要身份验证。所以我 运行 进入这篇文章,讨论我们如何在 .net 代码中实现它以便能够访问 UserInfo Endpoint。因此,在我的 TokenServices.cs
文件中,我执行了以下操作,但我不确定如何获取令牌本身。 我确实有一个方法 GetToken()
可以检索令牌,但我不确定我是否可以将该方法用于设置 Token = token
.[=32 的行=]
public async Task<TokenResponse> GetUserInfoToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = _discoveryDocument.UserInfoEndpoint,
// Token = token
});
if (tokenResponse.isError)
{
_logger.LogError($"Unable to get userinfo token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get UserInfo token", tokenResponse.Exception);
}
return tokenResponse;
}
这是完整的 class 文件:
namespace WeatherMVC.Services
{
public class TokenService : ITokenService
{
private readonly ILogger<TokenService> _logger;
private readonly IOptions<IdentityServerSettings> _identityServerSettings;
private readonly DiscoveryDocumentResponse _discoveryDocument;
public TokenService(ILogger<TokenService> logger, IOptions<IdentityServerSettings> identityServerSettings)
{
_logger = logger;
_identityServerSettings = identityServerSettings;
using var httpClient = new HttpClient();
_discoveryDocument = httpClient.GetDiscoveryDocumentAsync(identityServerSettings.Value.DiscoveryUrl).Result;
if (_discoveryDocument.IsError)
{
logger.LogError($"Unable to get discovery document. Error is: {_discoveryDocument.Error}");
throw new Exception("Unable to get discovery document", _discoveryDocument.Exception);
}
}
public async Task<TokenResponse> GetToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = _discoveryDocument.TokenEndpoint,
ClientId = _identityServerSettings.Value.ClientName,
ClientSecret = _identityServerSettings.Value.ClientPassword,
Scope = scope
});
if (tokenResponse.IsError)
{
_logger.LogError($"Unable to get token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get token", tokenResponse.Exception);
}
return tokenResponse;
}
public async Task<TokenResponse> GetUserInfoToken(string scope)
{
using var client = new HttpClient();
var tokenResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = _discoveryDocument.UserInfoEndpoint,
// Token = token
});
if (tokenResponse.isError)
{
_logger.LogError($"Unable to get userinfo token. Error is: {tokenResponse.Error}");
throw new Exception("Unable to get UserInfo token", tokenResponse.Exception);
}
return tokenResponse;
}
}
在我的HomeController
里面我有:
namespace WeatherMVC.Controllers
{
public class HomeController : Controller
{
private readonly ITokenService _tokenService;
private readonly ILogger<HomeController> _logger;
public HomeController(ITokenService tokenService, ILogger<HomeController> logger)
{
_tokenService = tokenService;
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[Authorize] // 25:44 in youtube video
public async Task<IActionResult> Weather()
{
var data = new List<WeatherData>();
using (var client = new HttpClient())
{
var tokenResponse = await _tokenService.GetToken("weatherapi.read");
client.SetBearerToken(tokenResponse.AccessToken);
var result = client
.GetAsync("https://localhost:5445/weatherforecast")
.Result;
if (result.IsSuccessStatusCode)
{
var model = result.Content.ReadAsStringAsync().Result;
data = JsonConvert.DeserializeObject<List<WeatherData>>(model);
return View(data);
}
else
{
throw new Exception("Unable to get content");
}
}
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration["InteractiveServiceSettings:AuthorityUrl"];
options.ClientId = Configuration["InteractiveServiceSettings:ClientId"];
options.ClientSecret = Configuration["InteractiveServiceSettings:ClientSecret"];
options.ResponseType = "code";
options.UsePkce = true;
options.ResponseMode = "query";
options.Scope.Add(Configuration["InteractiveServiceSettings:Scopes:0"]);
options.SaveTokens = true;
});
services.Configure<IdentityServerSettings>(Configuration.GetSection("IdentityServerSettings"));
services.AddSingleton<ITokenService, TokenService>();
}
WeatherApi Project
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication("Bearer", options =>
{
options.ApiName = "weatherapi";
options.Authority = "https://localhost:5443";
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "weatherapi", Version = "v1" });
});
}
Identity project
namespace identity
{
public static class Config
{
public static List<TestUser> Users
{
get
{
var address = new
{
street_address = "One Hacker Way",
locality = "Heidelberg",
postal_code = 69118,
country = "Germany"
};
return new List<TestUser>
{
new TestUser
{
SubjectId = "818727",
Username = "alice",
Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "admin"),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address),
IdentityServerConstants.ClaimValueTypes.Json)
}
},
new TestUser
{
SubjectId = "88421113",
Username = "bob",
Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "user"),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address),
IdentityServerConstants.ClaimValueTypes.Json)
}
}
};
}
}
public static IEnumerable<IdentityResource> IdentityResources =>
new[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new[]
{
new ApiScope("weatherapi.read"),
new ApiScope("weatherapi.write"),
};
public static IEnumerable<ApiResource> ApiResources => new[]
{
new ApiResource("weatherapi")
{
Scopes = new List<string> {"weatherapi.read", "weatherapi.write"},
ApiSecrets = new List<Secret> {new Secret("ScopeSecret".Sha256())},
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<Client> Clients =>
new[]
{
// m2m client credentials flow client
new Client
{
ClientId = "m2m.client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedScopes = {"weatherapi.read", "weatherapi.write"}
},
// interactive client using code flow + pkce
new Client
{
ClientId = "interactive",
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {"https://localhost:5444/signin-oidc" , "https://localhost:44394/signin-oidc"},
FrontChannelLogoutUri = "https://localhost:5444/signout-oidc",
PostLogoutRedirectUris = {"https://localhost:5444/signout-callback-oidc"},
AllowOfflineAccess = true,
AllowedScopes = {"openid", "profile", "weatherapi.read"},
RequirePkce = true,
RequireConsent = true,
AllowPlainTextPkce = false
},
};
}
}
是否有特定的方法可以调用令牌以便将令牌设置为该 UserInfo 令牌?任何 pointers/suggestions 将不胜感激!
已更新
要访问此屏幕(HomeController -> Weather()
),我必须获得授权,当我访问此页面时,无论不记名令牌持续多长时间,它都会让我保持登录状态。那么,为什么我无法访问 /connect/userinfo
页面?
使用options.SaveTokens = true; (在 AddOpenIDConnect 中)会将所有令牌保存在用户 cookie 中,然后您可以使用以下方式访问它:
var accessToken = await HttpContext.GetTokenAsync("access_token");
Sample code to get all the tokens if provided:
ViewBag.access_token = HttpContext.GetTokenAsync("access_token").Result;
ViewBag.id_token = HttpContext.GetTokenAsync("id_token").Result;
ViewBag.refresh_token = HttpContext.GetTokenAsync("refresh_token").Result;
ViewBag.token_type = HttpContext.GetTokenAsync("token_type").Result; //Bearer
ViewBag.expires_at = HttpContext.GetTokenAsync("expires_at").Result; // "2021-02-01T10:58:28.0000000+00:00"
发出请求的示例代码
var accessToken = await HttpContext.GetTokenAsync("access_token");
var authheader = new AuthenticationHeaderValue("Bearer", accessToken);
var client = new HttpClient();
var authheader = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Authorization = authheader;
var content = await client.GetStringAsync("https://localhost:7001/api/payment");
ViewBag.Json = JObject.Parse(content).ToString();
return View();