【问题标题】:.NET Core Api authentication using Identity server 4.NET Core Api 身份验证使用身份服务器 4
【发布时间】:2020-10-08 18:18:13
【问题描述】:

我正在尝试使用 .net core API 3.1 和 Identity server 4 创建一个小型电子商务演示应用程序。


Config.cs(Demo.Auth 项目)

    public static class Config
    {
        public static IEnumerable<IdentityResource> Ids =>
            new IdentityResource[]
            {                
                new IdentityResources.Profile(),
            };
        public static IEnumerable<ApiResource> ApiResources => new[]
        {
            new ApiResource("Demo.Api", "Demo Api")
        };

        public static IEnumerable<Client> Clients => new[]
        {
            new Client()
            {
                ClientId = "mvc",
                ClientName = "Demo.MvcClient",
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                RequirePkce = true,
                ClientSecrets =
                {
                    new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256())
                },
                RedirectUris = {"http://localhost:5003/signin-oidc"},
                FrontChannelLogoutUri = "http://localhost:5003/signout-oidc",
                PostLogoutRedirectUris = {"http://localhost:5003/signout-callback-oidc"},

                AllowOfflineAccess = true,
                AllowedScopes = {"profile"}
            }
        };
    }


Startup.cs(Demo.Auth 项目)

    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            IConfigurationRoot config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            string identityConnectionString = config.GetSection("ConnectionStrings")
                .Value;
            var migratingAssembly = typeof(Startup).GetTypeInfo()
                .Assembly.GetName()
                .Name;

            if (config.GetValue<bool>("UseInMemoryDatabase"))
            {
                services.AddIdentityServer(options =>
                    {
                        options.Events.RaiseErrorEvents = true;
                        options.Events.RaiseInformationEvents = true;
                        options.Events.RaiseFailureEvents = true;
                        options.Events.RaiseSuccessEvents = true;
                    })
                    .AddTestUsers(TestUsers.Users)
                    .AddInMemoryIdentityResources(Config.Ids)
                    .AddInMemoryApiResources(Config.ApiResources)
                    .AddInMemoryClients(Config.Clients)
                    .AddDeveloperSigningCredential();
            }
            else
            {
                services.AddIdentityServer(options =>
                    {
                        options.Events.RaiseErrorEvents = true;
                        options.Events.RaiseInformationEvents = true;
                        options.Events.RaiseFailureEvents = true;
                        options.Events.RaiseSuccessEvents = true;
                    })
                    .AddTestUsers(TestUsers.Users)
                    .AddDeveloperSigningCredential()
                    //This will store client and ApiResource
                    .AddConfigurationStore(options =>
                    {
                        options.ConfigureDbContext = b => b.UseSqlServer(identityConnectionString,
                            sql => sql.MigrationsAssembly(migratingAssembly));
                    })
                    //This will store token, consent or code
                    .AddOperationalStore(options =>
                    {
                        options.ConfigureDbContext = b => b.UseSqlServer(identityConnectionString,
                            sql => sql.MigrationsAssembly(migratingAssembly));
                    });
            }
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app,
            IWebHostEnvironment env)
        {
            // this will do the initial DB population
           // InitializeDatabase(app);

            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();

            app.UseRouting();
            app.UseIdentityServer();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/",
                    async context => { await context.Response.WriteAsync("Hello World!"); });
            });
        }       
    }


Startup.cs(API 项目)

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication("Bearer").AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
                options.ApiName = "Demo.Api";
            });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
        }
    }


WeatherForecastController(Demo.Api 项目的)

    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;
        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }


我在 postman 中测试了 API,它工作正常。 “Demo.Auth”项目正在生成令牌,我可以成功访问我的授权控制器。

这里的想法是:

MVC 客户端 ----> 身份服务器项目 ---> API

MVC 客户端想要访问 API。因此,我将在身份服务器项目上对 Mvc 客户端进行身份验证,如果他是有效用户,则生成令牌,然后我将调用我的 api。

注意:目前我正在使用 MVC 客户端,但稍后我会再添加一个客户端,可能是 Angular。

但我有一个问题。
如何将用户添加到我的数据库并验证数据库用户而不是测试用户。
我不明白的另一件事是我应该将登录和注册功能放在哪里以及该代码的外观。

我是身份服务器的新手,请原谅。

有人可以用一些代码指导我解决上述问题吗?提前致谢

【问题讨论】:

    标签: .net asp.net-core-webapi identityserver4 openid-connect


    【解决方案1】:

    如何将用户添加到我的数据库并验证数据库用户而不是测试用户。

    我不明白的另一件事是我应该在哪里放置登录和注册功能以及该代码的外观。

    有一种方法可以创建一个符合您要求的工作示例,主要使用来自 IdentityServer4 快速入门的实现。

    步骤是(使用 SQL 数据库):

    1. 使用 dotnet is4aspid 模板创建 mvc 核心项目。它将 IdentityServer 配置为项目的中间件,您可以通过准备迁移来更新数据库,以便为 ASP.NET Core Identity 和 IdentityServer 的登录、注销、同意、授予 (UI) 功能创建所有表。 (在数据库更新之前的 CreaeteIdentitySchema.cs 文件中,将标识列的注释替换为符合 SQL 数据库的注释: Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn) ,模板中的架构用于 SQLite 数据库)

    2. 在mvc核心项目的Startup.cs中激活Razore Pages,添加services.AddRazorPages()和endpoints.MapRazorPages(),然后就可以添加脚手架了,你可以添加所有需要注册和维护用户帐户的页面(本地和来自外部提供商)使用 Razor 类库。登录和注销页面应由 IdentityServer 控制以进行身份​​验证。

    3. 接下来,您可以使用 IdentityServer4.EntityFramework.Storage nuget 包中的 ConfigurationDbContext 、 PersistedGrantDbContext 上下文及其实体来创建迁移和更新现有的 ASP.NET 身份数据库,为客户端、资源和范围添加表,也为临时操作数据,例如作为授权码和刷新令牌。要向这些表添加、删除或更新数据,您可以使用这两个上下文手动创建接口。

    4. 最后一步是根据 Quickstarts 创建客户端和 Api 项目,并使用 IdentityServer 配置它们以供使用。

    Startup.cs 文件最后会是:

        public class Startup
    {
        public IWebHostEnvironment Environment { get; }
        public IConfiguration Configuration { get; }
    
        public Startup(IWebHostEnvironment environment, IConfiguration configuration)
        {
            Environment = environment;
            Configuration = configuration;
        }
    
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IEmailSender, EmailSender>();
    
            services.AddControllersWithViews();
    
            services.Configure<IISOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication = false;
            });
    
            services.Configure<IISServerOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication = false;
            });
    
            var migrationsAssembly =    typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
    
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    
            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            
            var builder = services.AddIdentityServer(options =>
                {
                    options.Events.RaiseErrorEvents = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents = true;
                    options.Events.RaiseSuccessEvents = true;
                })
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = b =>   b.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = b =>           b.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                .AddAspNetIdentity<ApplicationUser>();
    
            builder.AddDeveloperSigningCredential();
    
            services.AddRazorPages();
    
            services.AddAuthentication()
                .AddGoogle(options =>
                {
                    options.ClientId = "copy client ID from Google here";
                    options.ClientSecret = "copy client secret from Google here";
                });
        }
    
        public void Configure(IApplicationBuilder app)
        {
            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
    
            app.UseStaticFiles();
    
            app.UseRouting();
            app.UseIdentityServer();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
                endpoints.MapRazorPages();
            });
        }
    
    }  
    

    【讨论】:

    • 我真的很抱歉,但我完全不明白第二点。我记得上次我使用身份脚手架创建了一个 Razor MVC 应用程序,这对我来说效果很好。但在这里我很困惑。根据您的指导,我在 GitHub 上创建了一个公共存储库,并遵循了您上面提到的所有要点。它给了我“未经授权的客户端”的错误,我测试了我的代码,我能够生成令牌并能够访问 API 调用,但 MVC 客户端无法正常工作,真的不知道为什么会发生。
    • 在第 4 点中,您提到创建 api 和客户端项目,我补充说,但不明白第 2 点。在 Razor 页面(mvc 客户端)中并创建了“Login.cshtml”,但似乎没有什么对我有用。您能否在我的仓库中添加一些代码或建议我。请帮助我,不明白我哪里出错了。回购:github.com/Ashwani44/Sol_Ecommerce_Demo
    • Glenn Singh 在 mvc 客户端中不需要 Razor 页面。客户端重定向 (options.DefaultChallengeScheme = "oidc") 未经授权调用 AuthServer 项目,您在 Views/Account 文件夹中有登录和注销页面,它们带有 is4aspid 模板。在第二点中,我提到要在 AuthServer 项目中搭建 Razor 页面,以添加注册、删除...页面以及现有的登录和注销页面,这些页面应该保持在 IdentityServer 控制之下。 Demo.AuthServer 项目中缺少 Areas\Identity\Pages\Account 文件夹。
    【解决方案2】:

    在 ResourceOwnerPasswrod 流程中,您可以在客户端保留注册和登录功能,并且可以根据数据库用户验证用户。

    您应该实现自定义用户存储以验证用户并从数据库添加声明。如下更改启动代码,Userrepository 类代表数据库通信以验证用户并从数据库获取声明:

    更新身份配置的启动配置方法:

    var idServerServiceFactory = new IdentityServerServiceFactory()
    .UseInMemoryClients(Clients.Get())
    .UseInMemoryScopes(Scopes.Get())
    .AddCustomUserStore();
    

    添加以下类并根据您的要求进行更改:

    public static class CustomIdentityServerBuilderExtensions
    {
        public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder)
        {                   
            builder.AddProfileService<UserProfileService>();           
            builder.AddResourceOwnerValidator<UserResourceOwnerPasswordValidator>();
            return builder;
        }
    }
    
    public class UserProfileService : IProfileService
    {
        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
                UserRepository userRepository=new UserRepository();
                var user = userRepository.GetUserById(int.Parse(context.Subject.GetSubjectId()));
                if (user != null)
                {
                    var userTokenModel = _mapper.Map<UserTokenModel>(user);
                    var claims = new List<Claim>();
                    claims.Add(new Claim("UserId", user.UserId));
                    // Add another claims here 
                    context.IssuedClaims.AddRange(claims);                    
        }
        public async Task IsActiveAsync(IsActiveContext context)
        {          
        }
    }
    
    public class UserResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {        
        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {           
                UserRepository userRepository=new UserRepository();
                var userLoginStatus = userRepository.GetUserById(context.UserName, context.Password);
    
                if (userLoginStatus != null)
                {
    
                        context.Result = new GrantValidationResult(userLoginStatus.UserId.ToString(),
                             OidcConstants.AuthenticationMethods.Password);                   
                }
                else
                {                    
                    context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient, 
                            "Wrong Credentials");
                }            
        }
    }
    

    请参阅 ASP.NET CORE IDENTITYSERVER4 RESOURCE OWNER PASSWORD FLOW WITH CUSTOM USERREPOSITORY 了解 ResourceOwnerPasswrod 流程。建议将此流程用于支持旧应用程序。

    还有更多的流程:

    1. 隐式
    2. 混合
    3. 授权码

    更多详情请参考official documentation

    【讨论】:

    • 非常感谢您。我将创建一个存储库并测试此代码。由于我正在制作电子商务应用程序,我还想问一件事,我应该使用哪种授权类型?我在这里使用正确的授权类型吗?再次感谢!!
    • 您是在创建新应用还是在扩展旧应用。您可以在官方文档中查看应用类型的推荐。
    • 这是一个新的应用程序。好的,我会检查文档。
    • @Glennsingh 资源所有者流的最大缺点是用户和身份验证服务器必须完全信任客户端应用程序。如果您正在构建电子商务应用程序,那么您最好使用授权码流程
    • @Glennsingh 不推荐使用资源所有者流程的另一个原因是客户端应用程序必须处理用户密码。从安全角度来看,这很困难。
    【解决方案3】:

    创建和维护用户的责任在于身份验证服务器。

    我应该把登录和注册功能放在哪里

    因此,Identity Server 项目将包含注册、登录、忘记密码等端点。

    如何将用户添加到我的数据库并验证数据库用户而不是测试用户。

    微软身份核心

    您可以实现 Microsoft Identity Core,它提供了与帐户管理相关的所有功能。 IdentityServer4 中内置了对它的支持。

    这样您就不必担心代码或数据库。

    注意:Microsoft Identity Core 在后台做了很多事情,因此您将无法理解它与 IdentityServer4 的实际工作原理。

    您可以从here(打开 Startup.cs)找到示例代码,从here 找到文档。

    您还可以查看 Row Coding 的 this YouTube 系列。

    自定义用户存储库

    如果你想在不使用 Microsoft Identity Core 的情况下验证用户,那么你可以实现 IResourceOwnerPasswordValidator 接口,示例代码可以在这里找到here 和博客here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-15
      • 2017-06-26
      • 2020-09-01
      • 2017-08-25
      • 2020-07-04
      • 2017-07-26
      相关资源
      最近更新 更多