ASP.NET 使用 Openiddict 的核心 1.0 OAuth 服务器

ASP.NET Core 1.0 OAuth Server using Openiddict

我想使用 Openiddict OAuth 来保护我的 ASP.NET Core 1.0 Web 应用程序中的 api 端点。 api 端点将由 phone 应用程序调用,用户必须使用用户名和密码登录。

流程是这样的:

如何配置 Openiddict OAuth,以便使用 OAuth 保护 API 端点?

How can I configure Openiddict OAuth so I can protect the API endpoints using OAuth?

您的方案听起来很适合 the simple "resource owner password credentials" grant,它基本上是基本或表单身份验证的 OAuth2 等价物。

以下是我的推荐:

创建一个新的AccountController/RegistrationController API负责创建新账户的控制器:

由于这个阶段用户账号不存在,这里不能使用token认证(就像默认的AccountController.Register模板在用户注册前不能要求cookies认证一样)。

配置 OpenIddict 以启用令牌端点并允许资源所有者密码凭据授予:

services.AddOpenIddict<ApplicationDbContext>()
    // Disable the HTTPS requirement during development.
    .DisableHttpsRequirement()

    // Enable the token endpoint, required to use
    // the resource owner password credentials grant.
    .EnableTokenEndpoint("/connect/token")

    // Enable the password and the refresh token flows.
    .AllowPasswordFlow()
    .AllowRefreshTokenFlow();

使用 OAuth2 验证中间件来保护您的 APIs:

要启用令牌认证,请参考AspNet.Security.OAuth.Validation 1.0.0-alpha2-final 包并在app.UseMvc() 之前添加app.UseOAuthValidation()。要强制进行身份验证,只需像使用 cookie 身份验证一样使用 [Authorize] 属性。

不要犹豫,一起玩 this sample。它的客户端部分不使用移动应用程序,但您应该很容易理解它是如何工作的。

有关详细信息,您还可以阅读 Mike Rousos 为 Microsoft .NET Web 开发和工具博客撰写的博客 post:Bearer Token Authentication in ASP.NET Core

好的,感谢@Pinpoint 为我指明了正确的方向。

但是这是我的 Startup.cs 配置:

   public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        if (env.IsDevelopment())
        {
            // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
            builder.AddUserSecrets();
        }

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, ApplicationRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
          .DisableHttpsRequirement()
          .EnableTokenEndpoint("/connect/token")
          .AllowPasswordFlow()
          .AllowRefreshTokenFlow()
          .UseJsonWebTokens();

        services.AddMvc();

        // Add application services.
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();
    }

    // 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)
    {


        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();      

        app.UseIdentity();

        app.UseOpenIddict();

        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            RequireHttpsMetadata = false,
            Audience = "http://localhost:24624/",
            Authority = "http://localhost:24624/"
        });


        // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

ApplicationDbContext.cs:

  public class ApplicationDbContext : OpenIddictDbContext<ApplicationUser, ApplicationRole>
{
    public ApplicationDbContext(DbContextOptions options)
        : base(options)
    {
        Database.EnsureCreated();
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

ApplicationRole.cs:

public class ApplicationRole : IdentityRole
{
}

ApplicationUser.cs:

  public class ApplicationUser : OpenIddictUser
{
}

ServiceController.cs:

 [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[Route("api/service")]
public class ServiceController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;

    public ServiceController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpGet]
    [Route("getdata")] 
    public async Task<IActionResult> GetData()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null) return Ok("No user / not logged in");// if Authorize is not applied
        return Ok(user);
    }
}

这里的关键是ServiceController.cs:[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]

@Pinpoint:我没有使用 app.UseOAuthValidation() 因为它返回 302 并重定向到 Account/Login。

所以现在它是这样工作的:

  • 访问 http://domain.com,用户可以注册、登录、查看数据等
  • 用户可以下载移动应用程序、注册、登录和获取数据

在 api 端实现用户注册登录非常简单直接。

问题是使用 fiddler 并向 http://domain.com/api/service/getdata 发出 GET 返回 302 并重定向到 Account/Login。如果我删除 app.UseIdentity(),那么如果将返回 401 Unauthorized 但用户将无法再使用 UI http://domain.com 登录。将此 [Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)] 添加到我的 ServiceController 解决了问题。

@Pinpoint app.UseOAuthValidation() 的好处是什么?