我们在《ASP.NET Core项目实战的课程》第一章里面给identity server4做了一个全面的介绍和示例的练习 ,这篇文章是根据大家对OIDC遇到的一些常见问题整理得出。

本文将涉及到以下几个话题:

  • 什么是OpenId Connect (OIDC)

  • OIDC 对oAuth进行了哪些扩展?

  • Identity Server4提供的OIDC认证服务(服务端)

  • ASP.NET Core的权限体系中的OIDC认证框架(客户端)

什么是 OIDC

在了解OIDC之前,我们先看一个很常见的场景。假使我们现在有一个网站要集成微信或者新浪微博的登录,两者现在依然采用的是oAuth 2.0的协议来实现 。 关于微信和新浪微博的登录大家可以去看看它们的开发文档。

在我们的网站集成微博或者新浪微博的过程大致是分为五步:

  1. 准备工作:在微信/新浪微博开发平台注册一个应用,得到AppId和AppSecret

  2. 发起 oAauth2.0 中的 Authorization Code流程请求Code

  3. 根据Code再请求AccessToken(通常在我们应用的后端完成,用户不可见)

  4. 根据 AccessToken 访问微信/新浪微博的某一个API,来获取用户的信息

  5. 后置工作:根据用户信息来判断是否之前登录过?如果没有则创建一个用户并将这个用户作为当前用户登录(我们自己应用的登录逻辑,比如生成jwt),如果有了则用之前的用户登录。

中间第2到3的步骤为标准的oAuth2 授权码模式的流程,如果不理解的可以参考阮一峰所写的《理解oAuth2.0》一文。我们主要来看第4和5步,对于第三方应用要集成微博登录这个场景来说最重要的是我希望能快速拿到用户的一些基本信息(免去用户再次输入的麻烦)然后根据这些信息来生成一个我自己的用户跟微博的用户Id绑定(为的是下次你使用微博登录的时候我还能把你再找出来)。

oAuth在这里麻烦的地方是我还需要再请求一次API去获取用户数据,注意这个API和登录流程是不相干的,其实是属于微博开放平台丛多API中的一个,包括微信开放平台也是这样来实现。这里有个问题是前面的 2和3是oAuth2的标准化流程,而第4步却不是,但是大家都这么干(它是一个大家都默许的标准)

于是大家干脆就建立了一套标准协议并进行了一些优化,它叫OIDC

OIDC 建立在oAuth2.0协议之上,允许客户端(Clients)通过一个授权服务(Authorization Server)来完成对用户认证的过程,并且可以得到用户的一些基本信息包含在JWT中。

OIDC对oAuth进行了哪些扩展?

在oAuth2.0授权码模式的帮助下,我们拿到了用户信息。

以上没有认证的过程,只是给我们的应用授权访问一个API的权限,我们通过这个API去获取当前用户的信息,这些都是通过oAuth2的授权码模式完成的。 我们来看看oAuth2 授权码模式的流程:

第一步,我们向authorize endpoint请求code的时候所传递的response_type表示授权类型,原来只有固定值code

GET /connect/authorize?response_type=code&client_id=postman&state=xyz&scope=api1
&redirect_uri=http://localhost:5001/oauth2/callback

第二步,上面的请求执行完成之后会返回301跳转至我们传过去的redirect_uri并带上code

https://localhost:5001/oauth2/callback?code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c
&scope=api1
&state=111271607

  

第三步,用code换取access token

POST /connect/token?grant_type=authorization_code&code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c
&redirect_uri=http://localhost:5001/oauth2/callback
&client_id=postman
&client_secret=secret

  

通过这个POST我们就可以得到access_token

{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjV",
"expires_in": 3600,
"token_type": "Bearer"
}

  

我们拿到access_token之后,再把access_token放到authorization头请求 api来获取用户的信息。在这里,这个api不是属于授权服务器提供的,而是属于资源服务器。

OIDC给oAuth2进行扩展之后就填补了这个空白,让我们可以授权它添加了以下两个内容:

  • response_type 添加IdToken
  • 添加userinfo endpoint,用idToken可以获取用户信息

OIDC对它进行了扩展,现在你有三个选择:code, id_token和 token,现在我们可以这样组合来使用。

"response_type" value Flow
code Authorization Code Flow
id_token Implicit Flow
id_token token Implicit Flow
code id_token Hybrid Flow
code token Hybrid Flow
code id_token token Hybrid Flow

我们简单的来理解一下这三种模式:

  • Authorization Code Flow授权码模式:保留oAuth2下的授权模式不变response_type=code

  • Implicit Flow 隐式模式:在oAuth2下也有这个模式,主要用于客户端直接可以向授权服务器获取token,跳过中间获取code用code换accesstoken的这一步。在OIDC下,responsetype=token idtoken,也就是可以同时返回access_token和id_token。

  • Hybrid Flow 混合模式: 比较有典型的地方是从authorize endpoint 获取 code idtoken,这个时候id_token可以当成认证。而可以继续用code获取access_token去做授权,比隐式模式更安全。

    再来详细看一下这三种模式的差异:

Property Authorization Code Flow Implicit Flow Hybrid Flow
access token和id token都通过Authorization endpoint返回 no yes no
两个token都通过token end point 返回 yes no no
用户使用的端(浏览器或者手机)无法查看token yes no no
Client can be authenticated yes no yes
支持刷新token yes no yes
不需要后端参与 no yes no
       

我们来看一下通过Hybird如何获取 code、id_token、_以及access_token,然后再用id_token向userinfo endpoint请求用户信息。

第一步:获取code,

  • response_type=code id_token
  • scope=api1 openid profile 其中openid即为用户的唯一识别号
GET /connect/authorize?response_type=code id_token&client_id=postman&state=xyz&scope=api1 openid profile
&nonce=7362CAEA-9CA5-4B43-9BA3-34D7C303EBA7
&redirect_uri=http://localhost:5001/oauth2/callback

当我们使用OIDC的时候,我们请求里面多了一个nonce的参数,与state有异曲同工之妙。我们给它一个guid值即可。

第二步:我们的redirect_uri在接收的时候即可以拿到code 和 id_token

https://localhost:5001/oauth2/callback#
code=c5eaaaca8d4538f69f670a900d7a4fa1d1300b26ec67fba2f84129f0ab4ffa35
&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjVjMzA5ZGIwYTE2OGEwOTgGtpbj0GVXNnkKhGdrzA
&scope=openid%20profile%20api1&state=111271607

  

第三步:用code换access_token(这一步与oAuth2中的授权码模式一致)

第四步:用access_token向userinfo endpoint获取用户资料

Get http://localhost:5000/connect/userinfo
Authorization Bearer access_token

  

返回的用户信息

{
"name": "scott",
"family_name": "liu",
"sub": "5BE86359-073C-434B-AD2D-A3932222DABE"
}

  

以下是我们的流程示意图。

有人可能会注意到,在这里我们拿到的idtoken没有派上用场,我们的用户资料还是通过access_token从userinfo endpoint里拿的。这里有两个区别:

  1. userinfo endpoint是属于认证服务器实现的,并非资源服务器,有归属的区别
  2. id_token 是一个jwt,里面带有用户的唯一标识,我们在判断该用户已经存在的时候不需要再请求userinfo endpoint

下图是对id_token进行解析得到的信息:sub即subject_id(用户唯一标识 )

对jwt了解的同学知道它里面本身就可以存储用户的信息,那么id_token可以吗?答案当然是可以的,我们将在介绍完identity server4的集成之后最后来实现。

Identity Server4提供的OIDC认证服务

Identity Server4是asp.net core2.0实现的一套oAuth2 和OIDC框架,用它我们可以很快速的搭建一套自己的认证和授权服务。我们来看一下用它如何快速实现OIDC认证服务。

由于用户登录代码过多,完整代码可以加入ASP.NET Core QQ群 92436737获取。 此处仅展示配置核心代码。

过程

  • 新建asp.net core web应用程序
  • 添加identityserver4 nuget引用
  • 依赖注入初始化
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers());
  • 中间件添加
app.UseIdentityServer();
  • 配置

在测试的时候我们新建一个Config.cs来放一些配置信息

api resources

public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "API Application"){
UserClaims = { "role", JwtClaimTypes.Role }
}
};
}

identity resources

public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "API Application"){
UserClaims = { "role", JwtClaimTypes.Role }
}
};
}

clients

我们要讲的关键信息在这里,client有一个AllowGrantTypes它是一个string的集合。我们要写进去的值就是我们在上一节讲三种模式: Code,Implict和Hybird。因为这三种模式决定了我们的response_type可以请求哪几个值,所以这个地方一定不能写错。

IdentityServer4.Models.GrantTypes这个枚举给我们提供了一些选项,实际上是把oAuth的4种和OIDC的3种进行了组保。

public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "postman", AllowedGrantTypes = GrantTypes.Hybird,
RedirectUris = { "https://localhost:5001/oauth2/callback" }, ClientSecrets =
{
new Secret("secret".Sha256())
}, AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}, AllowOfflineAccess=true, },
};
}

users

public static List<TestUser> GetTestUsers()
{
return new List<TestUser> {
new TestUser {
SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
Username = "scott",
Password = "password",
Claims = new List<Claim> { new Claim(JwtClaimTypes.Name, "scott"),
new Claim(JwtClaimTypes.FamilyName, "liu"),
new Claim(JwtClaimTypes.Email, "scott@scottbrady91.com"),
new Claim(JwtClaimTypes.Role, "user"),
}
}
};
}

ASP.NET Core的权限体系中的OIDC认证框架

在Microsoft.AspNetCore.All nuget引用中包含了Microsoft.AspNetCore.Authentication.OpenIdConnect即asp.net core OIDC的客户端。我们需要在依赖注入中添加以下配置:

services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "postman";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});

Authority即我们的用identity server4搭建的认证授权服务器,而其中的GetClaimsFromUserInfoEndpoint则会在拿到id_token之后自动向userinfo endpoint请求用户信息并放到asp.net core的User Identity下。

我们上面讲过,可以不需要请求userinfo endpoint, 直接将用户信息放到id_token中。

这样我们就不需要再向userinfo endpoint发起请求,从id_token中即可以获取到用户的信息。而有了identity server4的帮助,完成这一步只需要一句简单的配置即可:

new Client
{
ClientId = "postman", AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess=true,
}

这样我们在拿到id_token之后,里即包含了我们的用户信息。

资料:

晓晨master的identity server4中文文档  http://www.cnblogs.com/stulzq/p/8119928.html李念辉身份认证核心:https://www.cnblogs.com/linianhui/archive/2017/05/30/openid-connect-core.html
OIDC协议: http://openid.net/specs/openid-connect-discovery-1_0.html
Jesse腾飞的asp.net core项目实战第一章identity server4准备 http://video.jessetalk.cn/course/5

Open ID Connect(OIDC)在 ASP.NET Core中的应用的更多相关文章

  1. 如何在ASP.NET Core中应用Entity Framework

    注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...

  2. [译]在Asp.Net Core 中使用外部登陆(google、微博...)

    原文出自Rui Figueiredo的博文<External Login Providers in ASP.NET Core> 摘要:本文主要介绍了使用外部登陆提供程序登陆的流程,以及身份 ...

  3. Asp.net Core中使用Redis 来保存Session, 读取配置文件

    今天 无意看到Asp.net Core中使用Session ,首先要使用Session就必须添加Microsoft.AspNetCore.Session包,默认Session是只能存去字节,所以如果你 ...

  4. C#调用接口注意要点 socket,模拟服务器、客户端通信 在ASP.NET Core中构建路由的5种方法

    C#调用接口注意要点   在用C#调用接口的时候,遇到需要通过调用登录接口才能调用其他的接口,因为在其他的接口需要在登录的状态下保存Cookie值才能有权限调用, 所以首先需要通过调用登录接口来保存c ...

  5. 【译】在Asp.Net Core 中使用外部登陆(google、微博...)

    原文出自Rui Figueiredo的博文<External Login Providers in ASP.NET Core> (本文很长) 摘要:本文主要介绍了使用外部登陆提供程序登陆的 ...

  6. ASP.NET Core 中的实时框架 SingalR

    目录 SignalR 是什么? 在 ASP.NET Core 中使用 SignalR 权限验证 横向扩展 源代码 参考 SignalR 是什么? ASP.NET Core SignalR 是一个开源的 ...

  7. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  8. 使用Redis Stream来做消息队列和在Asp.Net Core中的实现

    写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列,而不想引入新的服务,kafk ...

  9. ASP.NET Core 中文文档 第二章 指南(4.6)Controller 方法与视图

    原文:Controller methods and views 作者:Rick Anderson 翻译:谢炀(Kiler) 校对:孟帅洋(书缘) .张仁建(第二年.夏) .许登洋(Seay) .姚阿勇 ...

随机推荐

  1. 斐波那契数列第n项的值及前n项之和

    <script>// 算法题 // 题1:斐波那契数列:1.1.2.3.5.8.13.21...// // 一.斐波那契数列第n项的值 // // 方法一//递归的写法function a ...

  2. golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL

        先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和Map完成路由映射,数组的大小为8,下标为0的代表Get方法,以此类推,而数组的值则是Map,键为URL,值则是我们编写对应的接口.但 ...

  3. 系列博文-Three.js入门指南(张雯莉)-网格 setInterval方法 requestAnimationFrame方法 使用stat.js记录FPS

    第6章 动画 在本章之前,所有画面都是静止的,本章将介绍如果使用Three.js进行动态画面的渲染.此外,将会介绍一个Three.js作者写的另外一个库,用来观测每秒帧数(FPS). CSS3动画那么 ...

  4. Android 高仿微信6.0主界面 带你玩转切换图标变色

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41087219,本文出自:[张鸿洋的博客] 1.概述 学习Android少不了模仿 ...

  5. RocketMQ源码 — 十、 RocketMQ顺序消息

    RocketMQ本身支持顺序消息,在使用上发送顺序消息和非顺序消息有所区别 发送顺序消息 SendResult sendResult = producer.send(msg, new MessageQ ...

  6. HEOI2018——welcome to NOI2018

    我不得不和烈士和小丑走在同一道路上,  万人都要将火熄灭,  我一人独将此火高高举起,  我借此火得度一生的茫茫黑夜. ——海子 弹指一瞬间,翘首以盼的HEOI2018就来了. 我,一个滑稽的小丑,带 ...

  7. 用 fhq_Treap 实现可持久化平衡树

    支持对历史版本进行操作的平衡树 Treap 和 Splay 都是旋来旋去的 这样平衡树可持久化听起来不太好搞? 还有 fhq_Treap ! 每次涉及操作就复制一个节点出来 操作历史版本就继承它的根继 ...

  8. java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava

    搭建spring cloud的时候,报以下错误: java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplica ...

  9. 判断decimal 是否为整数

    用了半个小时搞懂了这个问题,基础愁死我了! private static boolean isIntegerValue(BigDecimal decimalVal) { return decimalV ...

  10. React 中阻止事件冒泡的问题

    在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行. JS 中事件的监听与处理 事件捕获与冒泡 DOM 事件会先后经历 捕获 与 冒泡 两个阶段.捕获即事件沿着 DOM 树由上往下传递,到达 ...