IdentityServer4系列 | 简化模式

一、前言

从上一篇关于资源密码凭证模式中,通过使用client_id和client_secret以及用户名密码通过应用Client(客户端)直接获取,从而请求获取受保护的资源,但是这种方式存在client可能存了用户密码这不安全性问题,所以需要做到client是高可信的应用。因此,我们可以考虑通过其他方式来解决这个问题。

我们通过Oauth2.0的「简化授权」模式了解到,可以使用这种方式来解决这个问题,让用户自己在IdentityServer服务器进行登录验证,客户端不需要知道用户的密码,从而实现用户密码的安全性。

所以在这一篇中,我们将通过多种授权模式中的「简化授权」模式进行说明,主要针对介绍「IdentityServer」保护API的资源,「简化授权」访问API资源。

二、初识

有些 Web 应用是纯前端应用,没有后端,必须将令牌储存在前端。RFC 6749 就规定了这种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"简化"(implicit)。

「简化模式」(implicit grant type)「不通过第三方应用程序的服务器」,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤(授权码模式后续会说明)。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

2.1 适用范围

这种模式的使用场景是基于浏览器的应用

这种模式基于安全性考虑,建议把token时效设置短一些, 不支持refresh token

2.2  授权流程:

 +----------+| Resource ||  Owner   ||          |+----------+^|(B)+----|-----+          Client Identifier     +---------------+|         -+----(A)-- & Redirection URI --->|               ||  User-   |                                | Authorization ||  Agent  -|----(B)-- User authenticates -->|     Server    ||          |                                |               ||          |<---(C)--- Redirection URI ----<|               ||          |          with Access Token     +---------------+|          |            in Fragment|          |                                +---------------+|          |----(D)--- Redirection URI ---->|   Web-Hosted  ||          |          without Fragment      |     Client    ||          |                                |    Resource   ||     (F)  |<---(E)------- Script ---------<|               ||          |                                +---------------++-|--------+|    |(A)  (G) Access Token|    |^    v+---------+|         ||  Client ||         |+---------+

「简化授权流程描述」

(A)客户端携带客户端标识以及重定向URI到授权服务器;

(B)用户确认是否要授权给客户端;

(C)授权服务器得到许可后,跳转到指定的重定向地址,并将令牌也包含在了里面;

(D)客户端不携带上次获取到的包含令牌的片段,去请求资源服务器;

(E)资源服务器会向浏览器返回一个脚本;

(F)浏览器会根据上一步返回的脚本,去提取在C步骤中获取到的令牌;

(G)浏览器将令牌推送给客户端。

2.2.1 过程详解


访问令牌请求

参数是否必须含义
response_type必需表示授权类型,此处的值固定为"token"
client_id必需客户端ID
redirect_uri可选表示重定向的URI
scope可选表示授权范围。
state可选表示随机字符串

「(1)资源服务器生成授权URL并将用户重定向到授权服务器」

  (用户的操作:用户访问https://resourcesServer/index.html跳转到登录地址,选择授权服务器方式登录)

在授权开始之前,它首先生成state参数(随机字符串)。client端将需要存储这个(cookie,会话或其他方式),以便在下一步中使用。

第一步,A 网站提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用。

https://oauth2Server/oauth2/default/v1/authorize?
response_type=token
&client_id=${clientId}
&redirect_uri=https://resourcesServer/implicit.html
&scope=授权范围
&state=随机字符串

生成的授权URL如上所述(如上),请求这个地址后重定向访问授权服务器,其中 response_type参数为token,表示直接返回令牌。

「(2)验证授权服务器登陆状态」

(用户的操作:如果未登陆用账号 User,密码12345登陆https://oauth2Server/login,如果已登陆授权服务器不需要此步骤)

如果未登陆账号,自动跳转到授权服务器登陆地址,登陆授权服务器以后用户被重定向client端

https://resourcesServer/implicit.html  

如已提前登陆授权服务器或授权服务器登陆会话还存在自动重定向到client端

https://resourcesServer/implicit.html

「(3)验证状态参数」

(用户的操作:无需操作)

用户被重定向回客户机,URL中现在有一个片段包含访问令牌以及一些其他信息。

用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B 网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为 URL 参数,传给 A 网站。

https://resourcesServer/authorization-code.html\#access_token=&token_type=Bearer&expires_in=3600&scope=photo&state=随机字符串

其中,token参数就是令牌,A网站因此直接在前端拿到令牌。

注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

用户使用这个令牌访问资源服务器,当令牌失效时使用刷新令牌去换取新的令牌

三、实践

在示例实践中,我们将创建一个授权访问服务,定义一个MVC客户端,MVC客户端通过「IdentityServer」上请求访问令牌,并使用它来访问API。

3.1 搭建 Authorization Server 服务

搭建认证授权服务

3.1.1 安装Nuget包

IdentityServer4 程序包

3.1.2 配置内容

建立配置内容文件Config.cs

    public static class Config{public static IEnumerable<IdentityResource> IdentityResources =>new IdentityResource[]{new IdentityResources.OpenId(),new IdentityResources.Profile(),};public static IEnumerable<ApiScope> ApiScopes =>new ApiScope[]{new ApiScope("Implicit_scope1")};public static IEnumerable<ApiResource> ApiResources =>new ApiResource[]{new ApiResource("api1","api1"){Scopes={ "Implicit_scope1" },ApiSecrets={new Secret("apipwd".Sha256())}  //api密钥}};public static IEnumerable<Client> Clients =>new Client[]{new Client{ClientId = "Implicit_client",ClientName = "Implicit Auth",AllowedGrantTypes = GrantTypes.Implicit,RedirectUris ={"http://localhost:5002/signin-oidc",  //跳转登录到的客户端的地址},PostLogoutRedirectUris ={"http://localhost:5002/signout-callback-oidc",//跳转登出到的客户端的地址},      AllowedScopes = {IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile,"Implicit_scope1"},// 是否需要同意授权 (默认是false)RequireConsent=true}, };}

RedirectUris : 登录成功回调处理的客户端地址,处理回调返回的数据,可以有多个。

PostLogoutRedirectUris :跳转登出到的客户端的地址。

这两个都是配置的客户端的地址,且是identityserver4组件里面封装好的地址,作用分别是登录,注销的回调

因为是「简化」授权的方式,所以我们通过代码的方式来创建几个测试用户。

新建测试用户文件TestUsers.cs

    public class TestUsers{public static List<TestUser> Users{get{var address = new{street_address = "One Hacker Way",locality = "Heidelberg",postal_code = 69118,country = "Germany"};return new List<TestUser>{new TestUser{SubjectId = "1",Username = "i3yuan",Password = "123456",Claims ={new Claim(JwtClaimTypes.Name, "i3yuan Smith"),new Claim(JwtClaimTypes.GivenName, "i3yuan"),new Claim(JwtClaimTypes.FamilyName, "Smith"),new Claim(JwtClaimTypes.Email, "i3yuan@email.com"),new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),new Claim(JwtClaimTypes.WebSite, "http://i3yuan.top"),new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)}}};}}}

返回一个TestUser的集合。

通过以上添加好配置和测试用户后,我们需要将用户注册到IdentityServer4服务中,接下来继续介绍。

3.1.3 注册服务

在startup.cs中ConfigureServices方法添加如下代码:

        public void ConfigureServices(IServiceCollection services){var builder = services.AddIdentityServer().AddTestUsers(TestUsers.Users); //添加测试用户// in-memory, code configbuilder.AddInMemoryIdentityResources(Config.IdentityResources);builder.AddInMemoryApiScopes(Config.ApiScopes);builder.AddInMemoryApiResources(Config.ApiResources);builder.AddInMemoryClients(Config.Clients);// not recommended for production - you need to store your key material somewhere securebuilder.AddDeveloperSigningCredential();}

3.1.4 配置管道

在startup.cs中Configure方法添加如下代码:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseIdentityServer();app.UseEndpoints(endpoints =>{endpoints.MapGet("/", async context =>{await context.Response.WriteAsync("Hello World!");});});}

以上内容是快速搭建简易IdentityServer项目服务的方式。

「这搭建 Authorization Server 服务跟上一篇资源密码凭证模式有何不同之处呢?」

  1. 在Config中配置客户端(client)中定义了一个AllowedGrantTypes的属性,这个属性决定了Client可以被哪种模式被访问,「GrantTypes.Implicit」「简化授权」。所以在本文中我们需要添加一个Client用于支持简化授权(「implicit」)。

  2. 「简化授权不通过第三方应用程序的服务器」,直接在浏览器中向认证服务器申请令牌,所有步骤在浏览器中完成,所以需要配置对应的回调地址和登出地址。这也是不同于之前的「资源所有者凭证模式」

3.2 搭建MVC 客户端

实现对客户端认证授权访问资源

3.2.1 快速搭建一个MVC项目

3.2.2 安装Nuget包

IdentityServer4.AccessTokenValidation 包

3.2.3 注册服务

要将对 OpenID Connect 身份认证的支持添加到MVC应用程序中。

在startup.cs中ConfigureServices方法添加如下代码:

    public void ConfigureServices(IServiceCollection services){services.AddControllersWithViews();services.AddAuthorization();services.AddAuthentication(options =>{options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "oidc";}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{options.Authority = "http://localhost:5001";options.RequireHttpsMetadata = false;options.ClientId = "Implicit_client";options.SaveTokens = true;options.GetClaimsFromUserInfoEndpoint = true;});}
  1. AddAuthentication注入添加认证授权,当需要用户登录时,使用 cookie 来本地登录用户(通过“Cookies”作为DefaultScheme),并将 DefaultChallengeScheme 设置为“oidc”,

  2. 使用 AddCookie 添加可以处理 cookie 的处理程序。

  3. 因为「简化模式」的实现是就是 OpenID Connect,所以在AddOpenIdConnect用于配置执行 OpenID Connect 协议的处理程序。Authority表明之前搭建的 IdentityServer 授权服务地址。然后我们通过ClientId。识别这个客户端。SaveTokens用于在 cookie 中保留来自IdentityServer 的令牌。

3.2.4 配置管道

然后要确保认证服务执行对每个请求的验证,加入UseAuthenticationUseAuthorizationConfigure中,在startup.cs中Configure方法添加如下代码:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}    app.UseRouting();app.UseCookiePolicy();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapDefaultControllerRoute();});}

UseAuthentication将身份验证中间件添加到管道中;

UseAuthorization 将启动授权中间件添加到管道中,以便在每次调用主机时执行身份验证授权功能。

3.2.5 添加授权

在HomeController控制器并添加[Authorize]特性到其中一个方法。在进行请求的时候,需进行认证授权通过后,才能进行访问。

        [Authorize]public IActionResult Privacy(){ViewData["Message"] = "Secure page.";return View();}

还要修改主视图以显示用户的Claim以及cookie属性。

@using Microsoft.AspNetCore.Authentication<h2>Claims</h2><dl>@foreach (var claim in User.Claims){<dt>@claim.Type</dt><dd>@claim.Value</dd>}
</dl><h2>Properties</h2><dl>@foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items){<dt>@prop.Key</dt><dd>@prop.Value</dd>}
</dl>

访问 Privacy 页面,跳转到认证服务地址,进行账号密码登录,Logout 用于用户的注销操作。

3.3 效果

3.3.1 项目测试

四、问题

4.1 SameSite策略

在Chrome浏览器中,进行认证授权的时候,用户登录之后,无法跳转到原网页,还是停留在登录页中,可以看控制台就发现上图的效果。

最后查找资料发现,是Google将于2020年2月份发布Chrome 80版本。本次发布将推进Google的“渐进改良Cookie”策略,打造一个更为安全和保障用户隐私的网络环境。所以本次更新可能导致浏览器无法向服务端发送Cookie。如果你有多个不同域名的应用,部分用户很有可能出现会话时常被打断的情况,还有部分用户可能无法正常登出系统。

所以我们需要解决这个问题:

方法一:将域名升级为 HTTPS

方法二:使用代码修改 SameSite 设置

新增 「SameSiteCookiesServiceCollectionExtensions」 类 (可以下载源码查看)

private const SameSiteMode Unspecified = (SameSiteMode)(-1);改为private const SameSiteMode Unspecified = SameSiteMode.Lax;

如果没有域名或内网环境,可以使用该方法,在 Startup 添加引用。

public IServiceProvider ConfigureServices(IServiceCollection services)
{...services.ConfigureNonBreakingSameSiteCookies();...

参考资料 Chrome80调整SameSite策略对IdentityServer4的影响以及处理方案

五、总结

  1. 本篇主要阐述以「简化授权」,编写一个MVC客户端,并通过客户端以浏览器的形式请求「IdentityServer」上请求获取访问令牌,从而访问资源。

  2. 「简化模式」解决了客户端模式用户身份验证和授权的问题,也解决了上一篇中「资源所有者密码凭证授权」面临的用户密码暴露的问题,是基于浏览器的应用。但由于token携带在url中,安全性方面不能保证,建议把token时效设置短一些

  3. 在后续会对在安全性方面做得更好的模式进行说明,数据库持久化问题,以及如何应用在API资源服务器中和配置在客户端中,会进一步说明。

  4. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。

  5. 项目地址

https://github.com/i3yuan/Yuan.IdentityServer4.Demo/tree/main/DiffAuthMode/ImplicitMVC

六、附加

「OpenID Connect」资料

「Implicit Grant资料」

「samesite问题解决」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306681.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

char截取字符串_字符串的排列(滑动窗口)

题目&#xff1a;给定两个字符串 s1 和 s2&#xff0c;写一个函数来判断 s2 是否包含 s1 的排列。换句话说&#xff0c;第一个字符串的排列之一是第二个字符串的子串。示例1&#xff1a;输入: s1 "ab" s2 "eidbaooo"输出: True解释: s2 包含 s1 的排列之…

c++中的全排列函数next_permutation()

全排列函数next_permutation() prev_permutation函数&#xff08;按降序排序&#xff09; 计算序列全排列的函数&#xff1a;next_permutation&#xff08;start,end&#xff09;&#xff0c;此函数求的是当前排列的下一个排列&#xff0c;这里的“下一个”&#xff0c;我们可…

学习搭建 Consul 服务发现与服务网格-有丰富的示例和图片

第一部分&#xff1a;Consul 基础1&#xff0c;Consul 介绍官网文档描述&#xff1a;Consul 是一个网络工具&#xff0c;提供功能齐全的服务网格和服务发现。它可以做什么&#xff1a;自动化网络配置&#xff0c;发现服务并启用跨任何云或运行时的安全连接。那么&#xff0c;我…

多线程并发如何高效实现生产者/消费者?

【导读】无需引入第三方消息队列组件&#xff0c;我们如何利用内置C#语法高效实现生产者/消费者对数据进行处理呢&#xff1f;在.NET Core共享框架&#xff08;Share Framework&#xff09;引入了通道&#xff08;Channel&#xff09;&#xff0c;也就是说无需额外通过NuGet包安…

js-cookie 无法设置cookie_php操作 cookie

1&#xff0c;设置cookie<?phpsetcookie(key);setcookie(key1,value1);setcookie(key2,value2,time()1*24*60*60);setcookie(key4, value4, time() 1 * 24 * 60 * 60, , , false, true); //一旦cookie的httponly为真&#xff0c;那么只能在服务端获取&#xff0c;js无法操…

.Net orm 开源项目 FreeSql 2.0.0

写在开头2018年11月头脑发热到今天&#xff0c;一晃已经两年&#xff0c;当初从舒服区走向一个巨大的坑&#xff0c;回头一看后背一凉。两年时间从无到有&#xff0c;经历数不清的日夜奋斗&#xff08;有人问花了多长时间投入&#xff0c;答案&#xff1a;全职x2 两年无休息&a…

c语言函数库——ispunct函数 判断字符是否为标点符号或特殊字符

c语言函数库——ispunct函数 判断字符是否为标点符号或特殊字符 头文件&#xff1a;#inlude <ctype.h> spunct() 函数用来检测一个字符是否为标点符号或特殊字符&#xff0c;其原型为&#xff1a; int ispunct(int c); 【参数】c 为需要检测的字符。 【返回值】若 c 为标…

js重新渲染div_前端工程师必备:从浏览器的渲染到性能优化

文章来自&#xff1a;华为云开发者社区摘要&#xff1a;本文主要讲谈及浏览器的渲染原理、流程以及相关的性能问题。问题前瞻1. 为什么css需要放在头部&#xff1f;2. js为什么要放在body后面&#xff1f;3. 图片的加载和渲染会阻塞页面DOM构建吗&#xff1f;4. dom解析完才出现…

做架构也得讲武德

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「169」篇原创敬上大家好&#xff0c;我是Z哥。今天分享一篇对「架构」这件事的随想。我想&#xff0c;做「架构」是每个热爱技术的技术人在不断追求想进入的领域。…

c++随机数函数rand()

c 语言rand()生成随机数 c语言中rand()函数生成随机数的用法&#xff1a; 详细介绍&#xff1a; (1)使用该函数首先应在开头包含头文件stdlib.h #include<stdlib.h>(C建议使用#include&#xff0c;下同) (2)在标准的C库中函数rand()可以生成0~RAND_MAX之间的一个随机数…

杂牌手柄模拟xboxone手柄_手机就能玩Switch游戏,蛋蛋模拟器+盖世小鸡X2手柄体验...

最近收到一个很爆炸的消息&#xff0c;国外大神开发出了EGG模拟器(蛋蛋模拟器)&#xff0c;让手机也能玩Switch游戏&#xff0c;一直垂涎Switch游戏体验的我怎么能错过呢&#xff0c;必须一探究竟。据悉&#xff0c;EGG模拟器支持100多款Switch游戏&#xff0c;而且游戏还在持续…

api-hook,更轻量的接口测试工具

前言在网站的开发过程中&#xff0c;接口联调和测试是至关重要的一环&#xff0c;其直接影响产品的核心价值&#xff0c;而目前也有许多技术方案和工具加持&#xff0c;让我们的开发测试工作更加便捷。接口作为数据传输的重要载体&#xff0c;数据格式和内容具有多样性&#xf…

C++11的for循环使用auto的新用法

C11的for循环使用auto的新用法 for(auto a:vec) { cout<<a<<" "; } #include<bits/stdc.h> using namespace std; int main() {vector<int> vec;for(int i0; i<10; i){vec.push_back(i);}for(auto a:vec){cout<<a<<" …

如何使用 C# 中的 HashSet

译文链接&#xff1a;https://www.infoworld.com/article/3586972/how-to-use-hashset-in-csharp.htmlHashSet 是一个优化过的无序集合&#xff0c;提供对元素的高速查找和高性能的set集合操作&#xff0c;而且 HashSet 是在 .NET 3.5 中被引入的&#xff0c;在 System.Collect…

python装饰器源代码_13-Python-装饰器

1、装饰器的定义 装饰器的本质就是函数&#xff0c;用来装饰其它函数&#xff0c;就是为其它函数添加附加功能。 装饰器原则如下&#xff1a; 不能修改被装饰的函数的源代码 不能修改被装饰的函数的调用方式 2、实现装饰器知识储备 函数即变量 1 defbar():2 print("in the…

算法设计与分析——分治与递归策略——hanoi问题

**汉诺塔问题&#xff1a;**古代有一个梵塔&#xff0c;塔内有三个座A、B、C&#xff0c;A座上有64个盘子&#xff0c;盘子大小不等&#xff0c;大的在下&#xff0c;小的在上&#xff08;如图&#xff09;。有一个和尚想把这64个盘子从A座移到B座&#xff0c;但每次只能允许移…

post多个参数_关于HTTP GET和POST的区别

Photo by Luca Bravo on UnsplashGET还是POST&#xff1f; 考虑将浏览器作为客户端&#xff0c;可以缓存哪种方法&#xff1f; 哪个是"安全"方法&#xff1f; 哪一个不是幂等的&#xff1f; 如果我将端点URL复制并粘贴到浏览器的地址栏中&#xff0c;然后按Enter&…

小试YARP

.net core下&#xff0c;一个轻量组反向代理库&#xff0c;由微软发起。做了一个简单的带验证的反向代理&#xff0c;应用结构如上图&#xff0c;一个验证服务&#xff0c;两个业务服务和一个YARP服务。源码https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/As…

Entity Framework Core 5中实现批量更新、删除

本文介绍了一个在EntityFramework Core 5中不需要预先加载数据而使用一句SQL语句批量更新、删除数据的开发包&#xff0c;并且分析了其实现原理&#xff0c;并且与其他实现方案做了比较。一、背景随着微软全面拥抱开源&#xff0c;.Net开源社区百花开放&#xff0c;涌现了非常多…

篮子里拿鸡蛋问题

一个一个拿&#xff0c;正好拿完。两个两个拿&#xff0c;还剩一个。三个三个拿&#xff0c;正好拿完。 四个四个拿&#xff0c;还剩一个。五个五个拿&#xff0c;还差一个。六个六个拿&#xff0c;还剩三个。 七个七个拿&#xff0c;正好拿完。八个八个拿&#xff0c;还剩一个…