第六节:IdentityServer4设备流授权模式和扫码登录(应用于IOT)
一. 模式探究
1.背景
在一些输入受限的设备上,要完成用户名和口令的输入是非常困难的,设备授权模式,可让用户登录到智能电视、 IoT 物联网设备或打印机等输入受限的设备。 若要启用此流,设备会让用户在另一台设备上的浏览器中访问一个网页,以进行登录。 用户登录后,设备可以获取所需的访问令牌和刷新令牌。
2.运行流程
图一:
图二:
大致流程:
1. 客户端通过携带ClientId、ClientSecret,请求IDS4服务器,请求成功,返回:DeviceCode、VerificationUriComplete
2. 客户端将url写入二维码,或者用浏览器直接打开,进入授权页面。这期间,客户端携带ClientId、ClientSecret、DeviceCode不断轮询请求IDS4服务器,看是否已经授权。
3. 上面的授权页面,用户输入账号、密码,确认授权。
4. 客户端通过轮询得知已经授权,且拿到返回值 accessToken。
5. 客户端携带accessToken,请求资源服务器。
参考:
微软OAuth 2.0 设备代码流:https://docs.microsoft.com/zh-cn/azure/active-directory/develop/v2-oauth2-device-code
百度Device授权模式:https://developer.baidu.com/wiki/index.php?title=docs/oauth/device
IDS4代码参考:https://damienbod.com/2019/02/20/asp-net-core-oauth-device-flow-client-with-identityserver4/
二. 代码实操与剖析
1. 项目准备
(1). IDS4_Server2: 授权认证服务器
(2). ResourceServer: 资源服务器
(3). WinformClient1:基于winform的客户端 (.Net下的,非Core下)
2.搭建步骤
(一).IDS4_Server2
(1).通过Nuget安装【IdentityServer4 4.0.2】程序集
(2).集成IDS4官方的UI页面
进入ID4_Server2的根目录,cdm模式下依次输入下面指令,集成IDS4相关的UI页面,发现新增或改变了【Quickstart】【Views】【wwwroot】三个文件夹
A.【dotnet new -i identityserver4.templates】
B.【dotnet new is4ui --force】 其中--force代表覆盖的意思, 空项目可以直接输入:【dotnet new is4ui】,不需要覆盖。
PS. 有时候正值版本更新期间,上述指令下载下来的文件可能不是最新的,这个时候只需要手动去下载,然后把上述三个文件夹copy到项目里即可
(下载地址:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI)
(3).创建配置类 Config1
A.配置api的范围集合:ApiScope, 在4.x版本中必须配置
B.配置需要保护Api资源:ApiResource,每个resource后面都需要配置对应的Scope
C.配置可以访问的客户端资源:Client。重点配置设备流模式:GrantTypes.DeviceFlow。
D.配置可以访问的用户资源:TestUser
代码分享:
public class Config1 { /// <summary> /// 声明api的Scope(范围)集合 /// IDS4 4.x版本必须写的 /// </summary> /// <returns></returns> public static IEnumerable<ApiScope> GetApiScopes() { List<ApiScope> scopeList = new List<ApiScope>(); scopeList.Add(new ApiScope("ResourceServer")); return scopeList; } /// <summary> /// 定义需要保护的Api资源 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { List<ApiResource> resources = new List<ApiResource>(); //ApiResource第一个参数是ServiceName,第二个参数是描述 resources.Add(new ApiResource("ResourceServer", "ResourceServer服务需要保护哦") { Scopes = { "ResourceServer" } }); return resources; } /// <summary> /// 定义可以使用ID4 Server 客户端资源 /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>() { new Client { ClientId = "client1",//客户端ID AllowedGrantTypes = GrantTypes.DeviceFlow, //验证类型:设备流模式 RequireConsent = true, //手动确认授权 ClientSecrets ={ new Secret("0001".Sha256())}, //密钥和加密方式 AllowedScopes = { "ResourceServer" }, //允许访问的api服务 AlwaysIncludeUserClaimsInIdToken=true } }; return clients; } /// <summary> /// 定义可以使用ID4的用户资源 /// </summary> /// <returns></returns> public static List<TestUser> GetUsers() { var address = new { street_address = "One Hacker Way", locality = "Heidelberg", postal_code = 69118, country = "Germany" }; return new List<TestUser>() { new TestUser { SubjectId = "001", Username = "ypf1", //账号 Password = "123456", //密码 Claims = { new Claim(JwtClaimTypes.Name, "Alice Smith"), new Claim(JwtClaimTypes.GivenName, "Alice"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://alice.com"), new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json) } }, new TestUser { SubjectId = "002", Username = "ypf2", Password = "123456", Claims = { new Claim(JwtClaimTypes.Name, "Bob Smith"), new Claim(JwtClaimTypes.GivenName, "Bob"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://bob.com"), //这是新的序列化模式哦 new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json) } } }; } }
(4).在Startup类中注册、启用、修改路由
A.在ConfigureService中进行IDS4的注册.
B.在Configure中启用IDS4 app.UseIdentityServer();
C.路由,这里需要注意,不要和原Controllers里冲突即可,该项目中没有Controllers文件夹,不要特别配置。
代码分享:
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.AddControllersWithViews(); services.AddIdentityServer() .AddDeveloperSigningCredential() //生成Token签名需要的公钥和私钥,存储在bin下tempkey.rsa(生产场景要用真实证书,此处改为AddSigningCredential) .AddInMemoryApiScopes(Config1.GetApiScopes()) //存储所有的scopes .AddInMemoryApiResources(Config1.GetApiResources()) //存储需要保护api资源 .AddTestUsers(Config1.GetUsers()) //存储用户信息 .AddInMemoryClients(Config1.GetClients()); //存储客户端模式(即哪些客户端可以用) } // 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(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseRouting(); //启用IDS4 app.UseIdentityServer(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } }