回顾

上一篇介绍了IdentityServer4客户端授权的方式,今天来看看IdentityServer4的基于密码验证的方式,与客户端验证相比,主要是配置文件调整一下,让我们来看一下

配置修改

  1. public static class Config
  2. {
  3. public static List<TestUser> GetUsers()
  4. {
  5. return new List<TestUser>
  6. {
  7. new TestUser
  8. {
  9. SubjectId = "1",
  10. Username = "alice",
  11. Password = "password"
  12. },
  13. new TestUser
  14. {
  15. SubjectId = "2",
  16. Username = "bob",
  17. Password = "password"
  18. }
  19. };
  20. }
  21. public static IEnumerable<IdentityResource> GetIdentityResources()
  22. {
  23. return new IdentityResource[]
  24. {
  25. new IdentityResources.OpenId()
  26. };
  27. }
  28. public static IEnumerable<ApiResource> GetApis()
  29. {
  30. return new List<ApiResource>
  31. {
  32. new ApiResource("api1", "My API")
  33. };
  34. }
  35. public static IEnumerable<Client> GetClients()
  36. {
  37. return new List<Client>
  38. {
  39. new Client
  40. {
  41. ClientId = "client",
  42. // no interactive user, use the clientid/secret for authentication
  43. AllowedGrantTypes = GrantTypes.ClientCredentials,
  44. // secret for authentication
  45. ClientSecrets =
  46. {
  47. new Secret("secret".Sha256())
  48. },
  49. // scopes that client has access to
  50. AllowedScopes = { "api1" }
  51. },
  52. // resource owner password grant client
  53. new Client
  54. {
  55. ClientId = "ro.client",
  56. AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
  57. ClientSecrets =
  58. {
  59. new Secret("secret".Sha256())
  60. },
  61. AllowedScopes = { "api1" }
  62. }
  63. };
  64. }
  65. }

通过上面的代码,与客户端授权方式相比,多了两个东西,一个是GetClients()方法中增加了一个Client,授权方式为资源拥有者密码的模式,另一个是增加了一个方法GetUsers(),真实场景中TestUser一般使用Asp.NetCore.Identity的用户,这里暂时使用TestUser来测试,IdentityServer4不是用户管理系统,它是授权框架(发放令牌的)

注册用户

在Startup中,把TestUser也添加上

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddIdentityServer()
  4. .AddDeveloperSigningCredential()
  5. .AddInMemoryClients(Config.GetClients())
  6. .AddInMemoryApiResources(Config.GetApis())
  7. .AddInMemoryIdentityResources(Config.GetIdentityResources())
  8. .AddTestUsers(Config.GetUsers());
  9. }

新建测试Api项目



可以选择删除IIS的设置,与前一篇文章的操作一致,并修改端口号为5001(Api资源服务地址)

并新增一个控制器IdentityController,继承自ControllerBase

  1. [Route("identity")]
  2. [Authorize]
  3. public class IdentityController : ControllerBase
  4. {
  5. public IActionResult Get()
  6. {
  7. return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
  8. }
  9. }

注意到IdentityController控制器类上有个特性[Authorize],这个代表这个控制器需要验证OK后才能访问,如果没有[Authorize]就说明访问不需要授权,在Get方法中这么写是为了获取到用户的身份信息,也就是Bearer Token中所包含的用户信息,当然也可以从Cookie中获取,我们使用Bearer Token的方式,以便照顾有移动客户端的场景

配置Api项目

  1. public class Startup
  2. {
  3. public void ConfigureServices(IServiceCollection services)
  4. {
  5. services.AddMvcCore()
  6. .AddAuthorization()
  7. .AddJsonFormatters();
  8. ///这里使用5000端口的授权服务端来验证
  9. services.AddAuthentication("Bearer")
  10. .AddJwtBearer("Bearer", options =>
  11. {
  12. options.Authority = "http://localhost:5000";
  13. options.RequireHttpsMetadata = false;
  14. options.Audience = "api1";
  15. });
  16. }
  17. public void Configure(IApplicationBuilder app)
  18. {
  19. ///这句别忘了,启用验证
  20. app.UseAuthentication();
  21. app.UseMvc();
  22. }
  23. }

启动

解决方案右键选择属性菜单并打开,设置启动方式,选择多个启动项目,启动,让两个服务都运行起来

验证测试

打开Postman,填入参数,提交,获取Token



拿到Token后,到5001地址去执行http://localhost:5001/identity,选择Bearer Token,把刚才获取到的Token填入,并点击红色按钮



点击Send 按钮后,Api执行成功



如果不填写Token,或者故意将Token填错将返回401,未授权错误



打开网址(https://jwt.io/),把Token复制进去,解析一下看看,与客户端授权方式相比,多了一个Sub

进一步思考

IdentityServer4应该有可以获取到用户信息的端口,我们从之前的发现端点里也能猜到一些,那我们拿着刚刚获取到的Token去这个端点获取下试试看



那我们就用Postman测试下,填写http://localhost:5000/connect/userinfo,使用Get



从上图可以看出,报403了,被拒绝访问了,可能哪里出了问题,经过一番搜索,授权服务端Config里GetClients()方法里ro.client这个用户,有个AllowedScopes = { "api1"},这里的权限可能不足

如下所示

  1. // resource owner password grant client
  2. new Client
  3. {
  4. ClientId = "ro.client",
  5. AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
  6. ClientSecrets =
  7. {
  8. new Secret("secret".Sha256())
  9. },
  10. AllowedScopes = { "api1"}
  11. }

调整后,允许的权限如下

AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId}

...期间重复的步骤省略,再次获取一次看看



这次OK了,看到一个sub,这个也貌似对应着TestUser里的SubjectId,这个是用户的唯一编号

  1. new TestUser
  2. {
  3. SubjectId = "1",
  4. Username = "alice",
  5. Password = "password",
  6. },

但是如果我想返回更多的用户信息怎么办呢,比如返回用户的电话号码,Email,以及自定义的类似组织等信息,应该如何处理呢,那我们给用户增加些身份(Claim)信息

  1. public static List<TestUser> GetUsers()
  2. {
  3. return new List<TestUser>
  4. {
  5. new TestUser
  6. {
  7. SubjectId = "1",
  8. Username = "alice",
  9. Password = "password",
  10. Claims = new Claim[]
  11. {
  12. new Claim(JwtClaimTypes.NickName,"Sarco"),
  13. new Claim(JwtClaimTypes.GivenName,"SarcoTest"),
  14. new Claim(JwtClaimTypes.PhoneNumber,"186221085730"),
  15. new Claim("org_code","3210")
  16. }
  17. },
  18. new TestUser
  19. {
  20. SubjectId = "2",
  21. Username = "bob",
  22. Password = "password"
  23. }
  24. };
  25. }

我们增加了四项身份信息,NickName,GivenName,PhoneNumber和OrgCode,其中第四项是自定义的,

同时修改下Client的AllowedScopes

  1. new Client
  2. {
  3. ClientId = "ro.client",
  4. AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
  5. ClientSecrets =
  6. {
  7. new Secret("secret".Sha256())
  8. },
  9. AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId,
  10. IdentityServerConstants.StandardScopes.Profile},
  11. }

再次获取Token



会发现报错了,上面scope我没有填写,和之前的一样,但是报错了,如果填写上,如api 或者 openid,可以成功,那看看不填有没有办法呢,经过一番研究,Config里GetIdentityResources()方法增加一项new IdentityResources.Profile()就可以了。

  1. public static IEnumerable<IdentityResource> GetIdentityResources()
  2. {
  3. return new IdentityResource[]
  4. {
  5. new IdentityResources.OpenId(),
  6. new IdentityResources.Profile()
  7. };
  8. }

再次获取下Token,并且根据Token再次获取用户信息



会发现,现在nickname和given_name有了,但是phone_number和org_code还是没有

报错原因:GetIdentityResources()方法添加后,只是说现在授权资源里包含Profile了,但是在GetClients()方法里的AllowedScopes里并不包含,所以报错

那么phone_number和org_code怎么没有出现呢?



我们看下Profile的范围,根据说明,它包含终端用户的默认身份信息有name,family_name,given_name,family_name,middle_name,nick_name,preferred_username,profile,picture,website,gender,birthdate,zoneinfo,locate and updated_at,也就是可以这么说,这么多的身份信息都属于Profile这个组里面,因为之前的用户信息里,我只添加了四项

  1. new Claim(JwtClaimTypes.NickName,"Sarco"),
  2. new Claim(JwtClaimTypes.GivenName,"SarcoTest"),
  3. new Claim(JwtClaimTypes.PhoneNumber,"186221085730"),
  4. new Claim("org_code","3210")

其中NickName和GivenName是属于Profile组的,所以,当客户的AllowedScopes里包含IdentityServerConstants.StandardScopes.Profile时,nick_name和given_name会显示出来,而PhoneNumber不属于Profile里,所以不会返回显示,自定义的组织信息肯定也无法获取到,还记得我前面说的

经过一番研究,Config里GetIdentityResources()方法增加一项new IdentityResources.Profile()就可以了,这是为什么呢?

记得之前获取Token的时候,Scope不填写的时候会报错,而填api或者openid就不会报错,是因为如果不填写,授权服务端就会从CliendId为"ro.client"的客户端拥有的Scope里全找一次,而我们一开始AllowedScopes里面包含了

IdentityServerConstants.StandardScopes.Profile,但是在GetIdentityResources()方法里没有添加new IdentityResources.Profile(),所以执行到获取Scope为IdentityServerConstants.StandardScopes.Profile时,由于找不到这项的IdentityResources,所以失败了,但是获取Token的时候填写api或者openid时,精确查找,由于AllowScopes和GetIdentityResources()都有,所以可以成功,这里可以这么说,AllowedScopes里的项必须在GetIdentityResources()或者GetApis()里里面要有

理解了这一点后,那我们调整下代码让自定义的org_code返回

  1. public static IEnumerable<IdentityResource> GetIdentityResources()
  2. {
  3. return new IdentityResource[]
  4. {
  5. new IdentityResources.OpenId(),
  6. new IdentityResources.Profile(),
  7. new IdentityResources.Phone(),
  8. new IdentityResource("org","组织代码",new string[]{"org_code" })
  9. };
  10. }
  11. public static IEnumerable<Client> GetClients()
  12. {
  13. return new List<Client>
  14. {
  15. new Client
  16. {
  17. ClientId = "client",
  18. // no interactive user, use the clientid/secret for authentication
  19. AllowedGrantTypes = GrantTypes.ClientCredentials,
  20. // secret for authentication
  21. ClientSecrets =
  22. {
  23. new Secret("secret".Sha256())
  24. },
  25. // scopes that client has access to
  26. AllowedScopes = { "api1" }
  27. },
  28. // resource owner password grant client
  29. new Client
  30. {
  31. ClientId = "ro.client",
  32. AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
  33. ClientSecrets =
  34. {
  35. new Secret("secret".Sha256())
  36. },
  37. ///org代表GetIdentityResources()里的自定义new IdentityResource("org","组织代码",new string[]{"org_code" }),而org_code代表用户Claims里的new Claim("org_code","3210")
  38. AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId,
  39. IdentityServerConstants.StandardScopes.Profile,"org"},
  40. }
  41. };
  42. }

调整后,再用postman获取Token,然后从http://localhost:5000/connect/userinfo里获取下用户信息



顺利的获取到了phone_number,org_code等信息

总结

IdentityResource与TestUser中的Claims的关系

TestUser中的多项Claim可以成为一个IdentityResource的一个项,也就是可以创建一个IdentityResource,包含User中的一个和多个Claim信息,类似于教师证里面包含工号和职位等多个相关信息

Client的AllowedScopes与IdentityResource以及ApiResource的关系

Client的AllowedScopes中的项必须在IdentityResource或者ApiResource中能找到,否则也会报错,ApiResource代表Api资源,IdentityResource项代表能访问用户身份信息包含哪些信息

IdentityServer4 学习笔记[2]-用户名密码验证的更多相关文章

  1. SQL反模式学习笔记20 明文密码

    目标:恢复或重置密码 反模式:使用明文存储密码 1.存储密码 使用明文存储密码或者在网络上传递密码是不安全的. 如果攻击者截取到你用来插入(或者修改)密码的sql语句,就可以获得密码.     黑客获 ...

  2. 【WCF】使用“用户名/密码”验证的合理方法

    我不敢说俺的方法是最佳方案,反正这世界上很多东西都是变动的,正像老子所说的——“反(返)者,道之动”.以往看到有些文章中说,为每个客户端安装证书嫌麻烦,就直接采用把用户名和密码塞在SOAP头中发送,然 ...

  3. WCF 安全性之 自定义用户名密码验证

    案例下载 http://download.csdn.net/detail/woxpp/4113172 客户端调用代码 通过代理类 代理生成 参见 http://www.cnblogs.com/woxp ...

  4. OpenVPN使用用户名/密码验证方式

    OpenVPN推荐使用证书进行认证,安全性很高,但是配置起来很麻烦.还好它也能像pptp等vpn一样使用用户名/密码进行认证. 不管何种认证方式,服务端的ca.crt, server.crt, ser ...

  5. 【WCF】Silverlight+wcf+自定义用户名密码验证

    本文摘自 http://www.cnblogs.com/virusswb/archive/2010/01/26/1656543.html 在昨天的博文Silverlight3+wcf+在不使用证书的情 ...

  6. WebService 用户名密码验证

    原文:WebService 用户名密码验证 在项目开发的过程中,WebService是经常要用的,当调用WebService方法时,需要经过服务的验证才可以调用,一般就是用户名/密码验证,还有一个就是 ...

  7. WCF服务安全控制之netTcpBinding的用户名密码验证【转】

    选择netTcpBinding WCF的绑定方式比较多,常用的大体有四种: wsHttpBinding basicHttpBinding netTcpBinding wsDualHttpBinding ...

  8. WCF用户名密码验证方式

    WCF使用用户名密码验证 服务契约 namespace WCFUserNameConstract { [ServiceContract] public interface IWcfContract { ...

  9. 自定义实现wcf的用户名密码验证

    目前wcf分为[传输层安全][消息层安全]两种,本身也自带的用户名密码验证的功能,但是ms为了防止用户名密码明文在网络上传输,所以,强制要求一旦使用[用户名密码]校验功能,则必须使用证书,按照常理讲, ...

随机推荐

  1. CDM中,实体与实体快捷方式之间的联系不能重复,否则会造成外键重复

    例如机场实体和跑道实体,例如: 在机场包中,跑道实体作为快捷方式出现,机场实体与跑道快捷方式实体间有连线关系,然而 在跑道包中,情况相反,但二者间也有连线.(模型原样) 要注意的是,虽然在两个包中都有 ...

  2. C语言学习笔记--内存操作常见错误

    1. 野指针 (1)指针变量中的值是非法的内存地址,进而形成野指针 (2)野指针不是 NULL 指针,是指向不可用内存地址的指针 (3)NULL 指针并无危害,很好判断,也很好调试 (4)C 语言中无 ...

  3. net.sf.fjep.fatjar_0.0.32 eclipse4.x 可以用的jar包

    http://pan.baidu.com/s/1nvlIw21?errno=0&errmsg=Auth%20Login%20Sucess&stoken=bb98db9f451c00ae ...

  4. AudioFormat

    AudioFormat   用于访问 一系列语音格式和通道配置常量 例如用于AudioTrack 和AudioRecord中 The AudioFormat class is used to acce ...

  5. HTML标签详细讲解

    http://www.cnblogs.com/yuanchenqi/articles/5603871.html

  6. 关于android通过shell修改文件权限的学习

    首先是文件的读写属性(下图): 要通过shel命令l修改文件权限: 1.首先在cmd里输入adb shell 命令进入编辑模式 2.用cd命令进入到想要修改的文件目录,不知道的时候可以用ls 命令列表 ...

  7. 保研机试训练[bailianoj]

    site:http://bailian.openjudge.cn/xly2018/ 1.计算任意两天之间的天数 思路:以0为起始点计算天数,然后相减即可.这样的编码复杂度会减少很多. #include ...

  8. ArcGIS Field Type /esriFieldTypeDate(转)

    ArcGIS Field Type   The following table outlines the equivalent field data types in ArcCatalog, ArcO ...

  9. WINFORM 开发模式,窗体回到默认样式方法。

    软件分为3类   客户端  网站应用  app WINFORM 主要用来只做客户端应用程序.C/S 客户端程序很重要的特点:可以操作用户电脑上的文件,执行在客户端上,电脑的配置越高执行就越流畅. 在p ...

  10. Java C++ Socket通讯

    import java.net.*; import javax.swing.plaf.SliderUI; /** * 与c语言通信(java做client,c/c++做server,传送一个结构) * ...