IdentityServer4的最佳使用

简介

  本人做微服务项目也有一段时间了,在微服务中让我感触颇深的就是这个IdentityServer4了,ID4(IdentityServer4的简称)中涉及的概念颇多,本文不谈概念(我怕读者没耐心看下去),在这分享下我个人的使用心得。

目录

  1. ID4简介

  2. ID4使用

ID4简介

  相信大家都知道面向对象中的封装(把通用的功能封装起来,减少程序中大量重复代码),我们知道在一个单体系统中有很多的重复模块,例如:身份认证、权限控制检查等,在单体系统中,这些都可以使用aop统一在一个地方控制。而在分布式系统,每个系统都需要进行身份认证、权限检查等。这时,每个系统都得写一套同样的代码来进行这些控制,我们能不能像单体系统那样在一个地方进行这些流程呢?这时我们可以使用IdentityServer4来实现。

  IdentityServer4是一个集成 身份认证和授权 的组件,使用OpenId Connect(身份识别框架) 和 Auth2.0(授权框架)来进行身份认证和授权的。

ID4使用

我这里只列出几个主要的类,其它,可以下载项目来看。关于如何使用,代码有点多,我比较懒,就没怎么讲解,感兴趣的小伙伴可以加个QQ:1983702356 来讨论下。

WEBAPPLICATION1 (IDENTITYSERVER4)项目,安装 INSTALL-PACKAGE IDENTITYSERVER4 -VERSION 2.5.0,

  1. 以下几个类比较关键

    1. Config.cs。主要是获取身份资源

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Config
      {
      public static IEnumerable<IdentityResource> GetIdentityResources()
      {
      return new List<IdentityResource>
      {
      new IdentityResources.OpenId(), //必须要添加,否则报无效的 scope 错误
      new IdentityResources.Profile(),
      new IdentityResources.Email()
      };
      }
      }
    2. TestClientStore.cs 加载IdentityServer4的client

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      /// <summary>
      /// 加载IdentityServer4的client
      /// </summary>
      public class TestClientStore : IClientStore
      {
      /// <summary>
      /// Service层中的一个接口
      /// </summary>
      public IClientService ClientSvc { get; set; }


      public TestClientStore(IClientService ClientSvc)
      {
      this.ClientSvc = ClientSvc;
      }
      public async Task<Client> FindClientByIdAsync(string clientId)
      {
      var dto = await ClientSvc.FindClientByIdAsync(clientId);
      if (dto==null)
      {
      return null;
      }
      var scopes = dto.APIResources.Select(e => e.Name).ToList();
      scopes.Add(IdentityServerConstants.StandardScopes.OpenId);
      scopes.Add(IdentityServerConstants.StandardScopes.Profile);
      return new Client
      {
      ClientId = dto.Client.Id,//API账号、客户端Id
      AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
      ClientSecrets =
      {
      new Secret(dto.Client.Secret.Sha256())//秘钥
      },
      AllowedScopes = scopes//这个账号支持访问哪些应用
      };
      }
      }
    3. TestReourceStore.cs,加载IdentityServer的APIResource

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      /// <summary>
      /// 加载IdentityServer的APIResource
      /// </summary>
      public class TestReourceStore : IResourceStore
      {
      public IApiResourceService resourceSvc { get; set; }
      public TestReourceStore(IApiResourceService resourceSvc)
      {
      this.resourceSvc = resourceSvc;
      }
      public async Task<ApiResource> FindApiResourceAsync(string name)
      {
      var entity = await resourceSvc.GetByNameAsync(name);
      return new ApiResource(entity.Name,entity.DisplayName);
      }

      public async Task<IEnumerable<ApiResource>> FindApiResourcesByScopeAsync(IEnumerable<string> scopeNames)
      {
      var list = await resourceSvc.GetDatasByNamesAsync(scopeNames);
      return list.Select(e=>new ApiResource(e.Name,e.DisplayName));
      }

      public async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeAsync(IEnumerable<string> scopeNames)
      {
      return Config.GetIdentityResources().Where(e => scopeNames.Contains(e.Name)).ToList();
      }

      public async Task<Resources> GetAllResourcesAsync()
      {
      var list = await resourceSvc.GetNoramlAll();

      var resouces = list.Select(e => new ApiResource(e.Name, e.DisplayName)).ToList();
      return new Resources
      {
      ApiResources = resouces
      };

      }
      }
    4. TestResourceOwnerPasswordValidator.cs,IdentityServer4登录验证

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      /// <summary>
      /// IdentityServer4登录验证
      /// </summary>
      public class TestResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
      {
      public IUserService UserSvc { get; set; }
      public TestResourceOwnerPasswordValidator(IUserService UserSvc)
      {
      this.UserSvc = UserSvc;
      }
      public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
      {
      string account = context.UserName;
      string pwd = context.Password;

      var loginResult = await UserSvc.Login(account, pwd);

      if (loginResult == null)
      {
      context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
      return;
      }
      context.Result = new GrantValidationResult(
      subject: context.UserName,
      authenticationMethod: "custom",
      claims: new Claim[] {
      new Claim("Name",context.UserName),
      new Claim("UserId",loginResult.Id),
      new Claim("Roles","Admin,Contact"), //模拟获取登录用户的角色信息
      new Claim("Premissions","List,Delete") //模拟获取登录用户的权限信息
      });

      }
      }
    5. ProfileService.cs,用户信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class ProfileService : IProfileService
      {
      public async Task GetProfileDataAsync(ProfileDataRequestContext context)
      {
      var claims = context.Subject.Claims.ToList();
      context.IssuedClaims = claims.ToList();
      }

      public async Task IsActiveAsync(IsActiveContext context)
      {
      context.IsActive = true;
      }
      }
  1. Startup类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

//services.AddAuthentication("Bearer")
// .AddIdentityServerAuthentication(options =>
// {
// options.Authority = "http://localhost:9500";//identity server 地址
// options.RequireHttpsMetadata = false;
// });

string conStr = Configuration["connectionString"];
services.AddDbContext<TestDbContext>(options =>
{
options.UseMySql(conStr);
});


///依赖注入Service层
AddSigletons(services);

#region ID4服务配置
var id4Build = services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources());
//加载apiresource
id4Build.Services.AddTransient<IResourceStore, TestReourceStore>();
//加载client
id4Build.Services.AddTransient<IClientStore, TestClientStore>();
//登录验证
id4Build.Services.AddTransient<IResourceOwnerPasswordValidator, TestResourceOwnerPasswordValidator>();
//加载profile。profile是用户信息
id4Build.Services.AddTransient<IProfileService, ProfileService>();
#endregion


//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);


}

public void AddSigletons(IServiceCollection services)
{
var assem = Assembly.Load("Test.Service");
var list = assem.GetTypes().Where(e => e.IsAbstract == false && typeof(ISignFac).IsAssignableFrom(e));
foreach (var instanType in list)
{
foreach (var item in instanType.GetInterfaces())
{
services.AddSingleton(item, instanType);
}
}
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
//插入id4中间件
app.UseIdentityServer();
//app.UseAuthentication();
//app.UseStaticFiles();
//app.UseCookiePolicy();
//app.UseMvc(routes =>
//{
// routes.MapRoute(
// name: "default",
// template: "{controller=Home}/{action=Index}/{id?}");
//});


}
}
  1. WebApplication3 API项目,修改Startup,并添加一个控制器,在方法上打上一个标签
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56

    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:9500";//identity server 地址
    options.RequireHttpsMetadata = false;
    });

    string conStr = Configuration["connectionString"];
    services.AddDbContext<TestDbContext>(options =>
    {
    options.UseMySql(conStr);
    });


    ///依赖注入Service层
    AddSigletons(services);
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void AddSigletons(IServiceCollection services)
    {
    var assem = Assembly.Load("Test.Service");
    var list = assem.GetTypes().Where(e => e.IsAbstract == false && typeof(ISignFac).IsAssignableFrom(e));
    foreach (var instanType in list)
    {
    foreach (var item in instanType.GetInterfaces())
    {
    services.AddSingleton(item, instanType);
    }
    }
    }
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    if (env.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    }
    app.UseAuthentication();

    app.UseMvc();
    }
    }

控制器

运行CONSOLEAPP1控制台项目, 生成数据库,添加几条数据

运行ConsoleApp1控制台项目

运行效果

  1. 在 WebApplication1 目录下运行cmd命令 dotnet run ,启动IdentitServer4,端口是9500

  2. 运行项目 WebApplication3 ,端口是5000

  3. 当我们直接调用 WebApplication3 API中的方法时,发现返回状态码为 401

  4. 请求Id4,复制返回的 access_token

  5. 再请求WebApplication3 API,并在报文头带上token

结束语

  个人认为我这种使用方式和其它使用方式最大的好处就是,可以写一个IdentityServer4的Client、APIResource增删改查,然后因为每次请求的时候都是从数据库读取数据的,如果数据被修改了,会立即生效。

原文地址:https://www.cnblogs.com/norain/p/IdentityServer.html