0

我正在嘗試爲我的aspnet核心Web應用程序生成訪問令牌。我創建了以下提供:使用AspNet時用戶始終爲空.Security.OpenIdConnect.Server

public class CustomOpenIdConnectServerProvider : OpenIdConnectServerProvider 
    { 
     public override Task ValidateTokenRequest(ValidateTokenRequestContext context) 
     { 
      // Reject the token requests that don't use grant_type=password or grant_type=refresh_token. 
      if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType()) 
      { 
       context.Reject(
        error: OpenIdConnectConstants.Errors.UnsupportedGrantType, 
        description: "Only the resource owner password credentials and refresh token " + 
           "grants are accepted by this authorization server"); 

       return Task.FromResult(0); 
      } 

      // Since there's only one application and since it's a public client 
      // (i.e a client that cannot keep its credentials private), call Skip() 
      // to inform the server the request should be accepted without 
      // enforcing client authentication. 
      context.Skip(); 

      return Task.FromResult(0); 
     } 

     public override async Task HandleTokenRequest(HandleTokenRequestContext context) 
     { 
      // Resolve ASP.NET Core Identity's user manager from the DI container. 
      var manager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>(); 

      // Only handle grant_type=password requests and let ASOS 
      // process grant_type=refresh_token requests automatically. 
      if (context.Request.IsPasswordGrantType()) 
      { 
       var user = await manager.FindByNameAsync(context.Request.Username); 
       if (user == null) 
       { 
        context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidGrant, 
         description: "Invalid credentials."); 

        return; 
       } 

       // Ensure the password is valid. 
       if (!await manager.CheckPasswordAsync(user, context.Request.Password)) 
       { 
        if (manager.SupportsUserLockout) 
        { 
         await manager.AccessFailedAsync(user); 
        } 

        context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidGrant, 
         description: "Invalid credentials."); 

        return; 
       } 

       if (manager.SupportsUserLockout) 
       { 
        await manager.ResetAccessFailedCountAsync(user); 
       } 

       var identity = new ClaimsIdentity(context.Options.AuthenticationScheme); 

       // Note: the name identifier is always included in both identity and 
       // access tokens, even if an explicit destination is not specified. 
       identity.AddClaim(ClaimTypes.NameIdentifier, await manager.GetUserIdAsync(user)); 

       identity.AddClaim(OpenIdConnectConstants.Claims.Subject, await manager.GetUserIdAsync(user)); 

       // When adding custom claims, you MUST specify one or more destinations. 
       // Read "part 7" for more information about custom claims and scopes. 
       identity.AddClaim("username", await manager.GetUserNameAsync(user), 
        OpenIdConnectConstants.Destinations.AccessToken, 
        OpenIdConnectConstants.Destinations.IdentityToken); 

       var claims = await manager.GetClaimsAsync(user); 
       foreach (var claim in claims) 
       { 
        identity.AddClaim(claim.Type, claim.Value, OpenIdConnectConstants.Destinations.AccessToken, 
         OpenIdConnectConstants.Destinations.IdentityToken); 
       } 

       // Create a new authentication ticket holding the user identity. 
       var ticket = new AuthenticationTicket(
        new ClaimsPrincipal(identity), 
        new AuthenticationProperties(), 
        context.Options.AuthenticationScheme); 


       // Set the list of scopes granted to the client application. 
       ticket.SetScopes(
        /* openid: */ OpenIdConnectConstants.Scopes.OpenId, 
        OpenIdConnectConstants.Scopes.OfflineAccess, 
        /* email: */ OpenIdConnectConstants.Scopes.Email, 
        /* profile: */ OpenIdConnectConstants.Scopes.Profile); 

       // Set the resource servers the access token should be issued for. 
       ticket.SetResources("resource_server"); 

       context.Validate(ticket); 
      } 
     } 

這一切正常,我可以得到的訪問令牌和用戶認證成功。我在這裏遇到的問題是,在我執行此操作時,在任何授權操作方法中:var user = await _userManager.GetUserAsync(User);user的值始終爲空!當然,我將Authorization標頭傳遞給一個有效的訪問令牌,然後請求將進入註釋爲Authorize的操作,沒有任何問題。這只是user的值爲空。任何人都可以告訴我我的代碼有什麼問題嗎?

回答

2

默認情況下,UserManager.GetUserAsync(User)使用ClaimTypes.NameIdentifier聲明作爲用戶標識符。

對於您的情況,ClaimTypes.NameIdentifier(OpenID Connect服務器中間件不再將其視爲1.0中的特殊聲明)不會添加到訪問令牌,因爲它沒有適當的目標。因此,Identity無法從訪問令牌中提取用戶標識符。

你有3個選項,以解決這個問題:

  • 通過在Startup.ConfigureServices()方法調用services.Configure<IdentityOptions>(options => options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject);通過更換標識使用的默認用戶標識要求。

  • 繼續使用ClaimTypes.NameIdentifier索賠,但給它正確的目的地(OpenIdConnectConstants.Destinations.AccessToken)。

  • 使用UserManager.FindByIdAsync(User.FindFirstValue(OpenIdConnectConstants.Claims.Subject))而不是UserManager.GetUserAsync(User)