2017-04-07 42 views
2

仍在ASP.NET Core中使用OpenIdDict(憑證流)定期掙扎OpenAuth,我更新到最新的OpenIdDict位和VS2017我的舊樣本您可以在https://github.com/Myrmex/repro-oidang找到代碼,並提供完整的逐步指導來創建基本啓動模板。希望這可以幫助社區幫助開始使用簡單的安全場景,所以對這個簡單示例代碼的任何貢獻都是值得歡迎的。OpenIdDict和ASP.NET Core:成功獲取令牌後的401(完全repro)

基本上我跟着憑證從OpenIdDict作者流樣品,並請求時,它才能讓我的令牌回來就好(使用招):

POST http://localhost:50728/connect/token 
Content-Type: application/x-www-form-urlencoded 

grant_type=password&scope=offline_access profile email roles&resource=http://localhost:4200&username=zeus&password=P4ssw0rd! 

問題是,當我嘗試使用此令牌,我一直得到401,沒有任何其他提示:沒有例外,沒有記錄。該請求是這樣的:

GET http://localhost:50728/api/values 
Content-Type: application/json 
Authorization: Bearer ... 

這裏是我的相關代碼:第一Startup.cs

public void ConfigureServices(IServiceCollection services) 
{ 
    // setup options with DI 
    // https://docs.asp.net/en/latest/fundamentals/configuration.html 
    services.AddOptions(); 

    // CORS (note: if using Azure, remember to enable CORS in the portal, too!) 
    services.AddCors(); 

    // add entity framework and its context(s) using in-memory 
    // (or use the commented line to use a connection string to a real DB) 
    services.AddEntityFrameworkSqlServer() 
     .AddDbContext<ApplicationDbContext>(options => 
     { 
      // options.UseSqlServer(Configuration.GetConnectionString("Authentication"))); 
      options.UseInMemoryDatabase(); 
      // register the entity sets needed by OpenIddict. 
      // Note: use the generic overload if you need 
      // to replace the default OpenIddict entities. 
      options.UseOpenIddict(); 
     }); 

    // register the Identity services 
    services.AddIdentity<ApplicationUser, IdentityRole>() 
     .AddEntityFrameworkStores<ApplicationDbContext>() 
     .AddDefaultTokenProviders(); 

    // configure Identity to use the same JWT claims as OpenIddict instead 
    // of the legacy WS-Federation claims it uses by default (ClaimTypes), 
    // which saves you from doing the mapping in your authorization controller. 
    services.Configure<IdentityOptions>(options => 
    { 
     options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name; 
     options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject; 
     options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role; 
    }); 

    // register the OpenIddict services 
    services.AddOpenIddict(options => 
    { 
     // register the Entity Framework stores 
     options.AddEntityFrameworkCoreStores<ApplicationDbContext>(); 

     // register the ASP.NET Core MVC binder used by OpenIddict. 
     // Note: if you don't call this method, you won't be able to 
     // bind OpenIdConnectRequest or OpenIdConnectResponse parameters 
     // to action methods. Alternatively, you can still use the lower-level 
     // HttpContext.GetOpenIdConnectRequest() API. 
     options.AddMvcBinders(); 

     // enable the endpoints 
     options.EnableTokenEndpoint("/connect/token"); 
     options.EnableLogoutEndpoint("/connect/logout"); 
     // http://openid.net/specs/openid-connect-core-1_0.html#UserInfo 
     options.EnableUserinfoEndpoint("/connect/userinfo"); 

     // enable the password flow 
     options.AllowPasswordFlow(); 
     options.AllowRefreshTokenFlow(); 

     // during development, you can disable the HTTPS requirement 
     options.DisableHttpsRequirement(); 

     // Note: to use JWT access tokens instead of the default 
     // encrypted format, the following lines are required: 
     // options.UseJsonWebTokens(); 
     // options.AddEphemeralSigningKey(); 
    }); 

    // add framework services 
    services.AddMvc() 
     .AddJsonOptions(options => 
     { 
      options.SerializerSettings.ContractResolver = 
       new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(); 
     }); 

    // seed the database with the demo user details 
    services.AddTransient<IDatabaseInitializer, DatabaseInitializer>(); 

    // swagger 
    services.AddSwaggerGen(); 
} 

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, 
    IDatabaseInitializer databaseInitializer) 
{ 
    loggerFactory.AddConsole(Configuration.GetSection("Logging")); 
    loggerFactory.AddDebug(); 
    loggerFactory.AddNLog(); 

    // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling 
    if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); 

    // to serve up index.html 
    app.UseDefaultFiles(); 
    app.UseStaticFiles(); 

    // CORS 
    // https://docs.asp.net/en/latest/security/cors.html 
    app.UseCors(builder => 
      builder.WithOrigins("http://localhost:4200") 
       .AllowAnyHeader() 
       .AllowAnyMethod()); 

    // add a middleware used to validate access tokens and protect the API endpoints 
    app.UseOAuthValidation(); 

    app.UseOpenIddict(); 

    app.UseMvc(); 

    // app.UseMvcWithDefaultRoute(); 
    // app.UseWelcomePage(); 

    // seed the database 
    databaseInitializer.Seed().GetAwaiter().GetResult(); 

    // swagger 
    // enable middleware to serve generated Swagger as a JSON endpoint 
    app.UseSwagger(); 
    // enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.) 
    app.UseSwaggerUi(); 
} 

然後我的控制器(你可以在上面引述的倉庫整體解決方案):

public sealed class AuthorizationController : Controller 
{ 
    private readonly IOptions<IdentityOptions> _identityOptions; 
    private readonly SignInManager<ApplicationUser> _signInManager; 
    private readonly UserManager<ApplicationUser> _userManager; 

    public AuthorizationController(
     IOptions<IdentityOptions> identityOptions, 
     SignInManager<ApplicationUser> signInManager, 
     UserManager<ApplicationUser> userManager) 
    { 
     _identityOptions = identityOptions; 
     _signInManager = signInManager; 
     _userManager = userManager; 
    } 

    private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user) 
    { 
     // Create a new ClaimsPrincipal containing the claims that 
     // will be used to create an id_token, a token or a code. 
     ClaimsPrincipal principal = await _signInManager.CreateUserPrincipalAsync(user); 

     // Create a new authentication ticket holding the user identity. 
     AuthenticationTicket ticket = new AuthenticationTicket(
      principal, new AuthenticationProperties(), 
      OpenIdConnectServerDefaults.AuthenticationScheme); 

     // Set the list of scopes granted to the client application. 
     // Note: the offline_access scope must be granted 
     // to allow OpenIddict to return a refresh token. 
     ticket.SetScopes(new[] { 
      OpenIdConnectConstants.Scopes.OpenId, 
      OpenIdConnectConstants.Scopes.Email, 
      OpenIdConnectConstants.Scopes.Profile, 
      OpenIdConnectConstants.Scopes.OfflineAccess, 
      OpenIddictConstants.Scopes.Roles 
     }.Intersect(request.GetScopes())); 

     ticket.SetResources("resource-server"); 

     // Note: by default, claims are NOT automatically included in the access and identity tokens. 
     // To allow OpenIddict to serialize them, you must attach them a destination, that specifies 
     // whether they should be included in access tokens, in identity tokens or in both. 
     foreach (var claim in ticket.Principal.Claims) 
     { 
      // Never include the security stamp in the access and identity tokens, as it's a secret value. 
      if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) 
       continue; 

      List<string> destinations = new List<string> 
      { 
       OpenIdConnectConstants.Destinations.AccessToken 
      }; 

      // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. 
      // The other claims will only be added to the access_token, which is encrypted when using the default format. 
      if (claim.Type == OpenIdConnectConstants.Claims.Name && 
       ticket.HasScope(OpenIdConnectConstants.Scopes.Profile) || 
       claim.Type == OpenIdConnectConstants.Claims.Email && 
       ticket.HasScope(OpenIdConnectConstants.Scopes.Email) || 
       claim.Type == OpenIdConnectConstants.Claims.Role && 
       ticket.HasScope(OpenIddictConstants.Claims.Roles)) 
      { 
       destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); 
      } 

      claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken); 
     } 

     return ticket; 
    } 

    [HttpPost("~/connect/token"), Produces("application/json")] 
    public async Task<IActionResult> Exchange(OpenIdConnectRequest request) 
    { 
     // if you prefer not to bind the request as a parameter, you can still use: 
     // OpenIdConnectRequest request = HttpContext.GetOpenIdConnectRequest(); 

     Debug.Assert(request.IsTokenRequest(), 
      "The OpenIddict binder for ASP.NET Core MVC is not registered. " + 
      "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); 

     if (!request.IsPasswordGrantType()) 
     { 
      return BadRequest(new OpenIdConnectResponse 
      { 
       Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, 
       ErrorDescription = "The specified grant type is not supported." 
      }); 
     } 

     ApplicationUser user = await _userManager.FindByNameAsync(request.Username); 
     if (user == null) 
     { 
      return BadRequest(new OpenIdConnectResponse 
      { 
       Error = OpenIdConnectConstants.Errors.InvalidGrant, 
       ErrorDescription = "The username/password couple is invalid." 
      }); 
     } 

     // Ensure the user is allowed to sign in. 
     if (!await _signInManager.CanSignInAsync(user)) 
     { 
      return BadRequest(new OpenIdConnectResponse 
      { 
       Error = OpenIdConnectConstants.Errors.InvalidGrant, 
       ErrorDescription = "The specified user is not allowed to sign in." 
      }); 
     } 

     // Reject the token request if two-factor authentication has been enabled by the user. 
     if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) 
     { 
      return BadRequest(new OpenIdConnectResponse 
      { 
       Error = OpenIdConnectConstants.Errors.InvalidGrant, 
       ErrorDescription = "The specified user is not allowed to sign in." 
      }); 
     } 

     // Ensure the user is not already locked out. 
     if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) 
     { 
      return BadRequest(new OpenIdConnectResponse 
      { 
       Error = OpenIdConnectConstants.Errors.InvalidGrant, 
       ErrorDescription = "The username/password couple is invalid." 
      }); 
     } 

     // Ensure the password is valid. 
     if (!await _userManager.CheckPasswordAsync(user, request.Password)) 
     { 
      if (_userManager.SupportsUserLockout) 
       await _userManager.AccessFailedAsync(user); 

      return BadRequest(new OpenIdConnectResponse 
      { 
       Error = OpenIdConnectConstants.Errors.InvalidGrant, 
       ErrorDescription = "The username/password couple is invalid." 
      }); 
     } 

     if (_userManager.SupportsUserLockout) 
      await _userManager.ResetAccessFailedCountAsync(user); 

     // Create a new authentication ticket. 
     AuthenticationTicket ticket = await CreateTicketAsync(request, user); 

     var result = SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); 
     return result; 
     // return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); 
    } 

    [HttpGet("~/connect/logout")] 
    public async Task<IActionResult> Logout() 
    { 
     // Extract the authorization request from the ASP.NET environment. 
     OpenIdConnectRequest request = HttpContext.GetOpenIdConnectRequest(); 

     // Ask ASP.NET Core Identity to delete the local and external cookies created 
     // when the user agent is redirected from the external identity provider 
     // after a successful authentication flow (e.g Google or Facebook). 
     await _signInManager.SignOutAsync(); 

     // Returning a SignOutResult will ask OpenIddict to redirect the user agent 
     // to the post_logout_redirect_uri specified by the client application. 
     return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme); 
    } 

    // http://openid.net/specs/openid-connect-core-1_0.html#UserInfo 
    [Authorize] 
    [HttpGet("~/connect/userinfo")] 
    public async Task<IActionResult> GetUserInfo() 
    { 
     ApplicationUser user = await _userManager.GetUserAsync(User); 

     // to simplify, in this demo we just have 1 role for users: either admin or editor 
     string sRole = await _userManager.IsInRoleAsync(user, "admin") 
      ? "admin" 
      : "editor"; 

     // http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims 
     return Ok(new 
     { 
      sub = user.Id, 
      given_name = user.FirstName, 
      family_name = user.LastName, 
      name = user.UserName, 
      user.Email, 
      email_verified = user.EmailConfirmed, 
      roles = sRole 
     }); 
    } 
} 

回答

1

正如在blog post中所提到的,OpenIddict使用的令牌格式最近略有改變,這使得最新的OpenIddict位發出的令牌不兼容您正在使用的舊版OAuth2驗證中間件版本。

移植到AspNet.Security.OAuth.Validation1.0.0它應該工作。

+0

謝謝,我沒有注意到這個帖子。這解決了這個問題。希望這可以對像我這樣的其他auth-newbies有用:) – Naftis

相關問題