检索 UserInfo 的访问令牌

Posted

技术标签:

【中文标题】检索 UserInfo 的访问令牌【英文标题】:Retrieving access token for UserInfo 【发布时间】:2021-09-05 13:15:24 【问题描述】:

我希望能够访问 UserInfo 端点/connect/userinfo,但是当我这样做时,它显示 401... 暗示需要身份验证。所以我遇到了这篇文章,它讨论了我们如何在 .net 代码中实现它,以便能够访问UserInfo Endpoint。因此,在我的 TokenServices.cs 文件中,我执行了以下操作,但我不确定如何获取令牌本身。 我确实有一个方法 GetToken() 可以检索令牌,但我不确定是否可以将该方法用于设置 Token = token 的行。

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;
        

这是完整的类文件:

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
        ,
          ;
    

有没有一种特定的方式可以调用令牌,以便我可以将 Token 设置为那个 UserInfo 令牌?任何指针/建议将不胜感激!


更新

要访问此屏幕 (HomeController -> Weather()),我必须获得授权,并且当我访问此页面时,无论持有者令牌说它持续多长时间,它都会让我保持登录状态。那么为什么我无法访问/connect/userinfo 页面呢?

【问题讨论】:

你的GetToken()是什么?你可以先试试看tokenResponse.isError @YiyiYou GetToken() 与 curl cmd 获取令牌的方式相同。 tokenResponse.isError 是假的。请查看我更新的帖子,以便您了解tokenResponse 的内容。 【参考方案1】:

选项的使用.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();

【讨论】:

谢谢,有没有具体的地方需要我实现这个? 我的代码展示了如何获取令牌。每次需要调用受访问令牌保护的 API 时,都需要执行上述操作。 您的示例代码,这与我在 TokenService.cs 内部所做的事情不同吗? -> GetToken() 方法转换为 HomeController.cs -> Weather() 方法?检查更新的部分 为什么同时使用客户端凭证流和授权码流?当您使用客户端凭据流登录令牌服务时,不涉及任何用户,因此调用 userinfo 端点似乎毫无意义? (不确定这是否可能)。我的代码基于使用 AddOpenIDConnect 返回的令牌。它将根据登录的用户获得自己的令牌。您通常不需要同时使用两者来保护典型的基于 Web 的应用程序。客户信誉。流仅用于不涉及用户的服务到服务通信。 一种选择是在您的后端 ASP.NET 核心中执行身份验证部分,然后在登录后跳回 Blazor。并非所有东西都必须在 Blazor 中制作。 :-) 只是为了保持简单:-)

以上是关于检索 UserInfo 的访问令牌的主要内容,如果未能解决你的问题,请参考以下文章

无法访问 Azure 上的 OpenId UserInfo 端点(AADSTS90010:JWT 令牌不能与 UserInfo 端点一起使用)

KeyCloak /userinfo 不返回用户信息,返回似乎是一个令牌

如何自定义Spring 授权服务器的 UserInfo 端点

userInfo 通知数据不可检索?

Xcode 从 NSNotification 检索 userInfo

Keycloak 直接访问授权在 keycloak userinfo 端点上无效