Open ID Connect(OIDC)在 ASP.NET Core中的应用
我们在《ASP.NET Core项目实战的课程》第一章里面给identity server4做了一个全面的介绍和示例的练习 ,这篇文章是根据大家对OIDC遇到的一些常见问题整理得出。
本文将涉及到以下几个话题:
什么是OpenId Connect (OIDC)
OIDC 对oAuth进行了哪些扩展?
Identity Server4提供的OIDC认证服务(服务端)
ASP.NET Core的权限体系中的OIDC认证框架(客户端)
什么是 OIDC
在了解OIDC之前,我们先看一个很常见的场景。假使我们现在有一个网站要集成微信或者新浪微博的登录,两者现在依然采用的是oAuth 2.0的协议来实现 。 关于微信和新浪微博的登录大家可以去看看它们的开发文档。
在我们的网站集成微博或者新浪微博的过程大致是分为五步:
准备工作:在微信/新浪微博开发平台注册一个应用,得到AppId和AppSecret
发起 oAauth2.0 中的 Authorization Code流程请求Code
根据Code再请求AccessToken(通常在我们应用的后端完成,用户不可见)
根据 AccessToken 访问微信/新浪微博的某一个API,来获取用户的信息
后置工作:根据用户信息来判断是否之前登录过?如果没有则创建一个用户并将这个用户作为当前用户登录(我们自己应用的登录逻辑,比如生成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里拿的。这里有两个区别:
- userinfo endpoint是属于认证服务器实现的,并非资源服务器,有归属的区别
- 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中的应用的更多相关文章
- 如何在ASP.NET Core中应用Entity Framework
注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...
- [译]在Asp.Net Core 中使用外部登陆(google、微博...)
原文出自Rui Figueiredo的博文<External Login Providers in ASP.NET Core> 摘要:本文主要介绍了使用外部登陆提供程序登陆的流程,以及身份 ...
- Asp.net Core中使用Redis 来保存Session, 读取配置文件
今天 无意看到Asp.net Core中使用Session ,首先要使用Session就必须添加Microsoft.AspNetCore.Session包,默认Session是只能存去字节,所以如果你 ...
- C#调用接口注意要点 socket,模拟服务器、客户端通信 在ASP.NET Core中构建路由的5种方法
C#调用接口注意要点 在用C#调用接口的时候,遇到需要通过调用登录接口才能调用其他的接口,因为在其他的接口需要在登录的状态下保存Cookie值才能有权限调用, 所以首先需要通过调用登录接口来保存c ...
- 【译】在Asp.Net Core 中使用外部登陆(google、微博...)
原文出自Rui Figueiredo的博文<External Login Providers in ASP.NET Core> (本文很长) 摘要:本文主要介绍了使用外部登陆提供程序登陆的 ...
- ASP.NET Core 中的实时框架 SingalR
目录 SignalR 是什么? 在 ASP.NET Core 中使用 SignalR 权限验证 横向扩展 源代码 参考 SignalR 是什么? ASP.NET Core SignalR 是一个开源的 ...
- 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权
OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...
- 使用Redis Stream来做消息队列和在Asp.Net Core中的实现
写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列,而不想引入新的服务,kafk ...
- ASP.NET Core 中文文档 第二章 指南(4.6)Controller 方法与视图
原文:Controller methods and views 作者:Rick Anderson 翻译:谢炀(Kiler) 校对:孟帅洋(书缘) .张仁建(第二年.夏) .许登洋(Seay) .姚阿勇 ...
随机推荐
- 渐进式Web应用(PWA)入门教程(上)
最近关于渐进式Web应用有好多讨论,有一些人还在质疑渐进式Web应用是否就是移动端未来. 但在这篇文章中我并不会将渐进式APP和原生的APP进行比较,但有一点是可以肯定的,这两种APP的目标都是使用户 ...
- HTML5 CSS3 诱人的实例 :canvas 模拟实现电子彩票刮刮乐
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/34089553 今天给大家带来一个刮刮乐的小例子~基于HTML5 canvas的, ...
- PHP引用符&的用法举例
php的引用就是在变量或者函数.对象等前面加上&符号.在PHP 中引用的意思是:不同的名字访问同一个变量内容.与C语言中的指针是有差别的,C语言中的指针里面存储的是变量的内容在内存中存放的地址 ...
- java程序运行结果
下面这段代码的运行结果是:AB.B 分析原因:也就是说在你的operate()方法中,参数是引用传递,也就是x,y分别为a,b引用的拷贝,在方法中,你给x追加了值,也就相应的改变了a的值,但是第二条语 ...
- ApplicationContextAware 接口的作用
接口说明:当一个类实现了这个接口之后,这个类就可以方便地获得 ApplicationContext 中的所有bean.换句话说,就是这个类可以直接获取Spring配置文件中,所有有引用到的bean对象 ...
- BZOJ_3585_mex && BZOJ_3339_Rmq Problem_莫队+分块
BZOJ_3585_mex && BZOJ_3339_Rmq Problem_莫队+分块 Description 有一个长度为n的数组{a1,a2,...,an}.m次询问,每次询问一 ...
- BZOJ_4551_[Tjoi2016&Heoi2016]树_树剖+线段树
BZOJ_4551_[Tjoi2016&Heoi2016]树_树剖+线段树 Description 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为 ...
- 【毕业原版】-《巴斯大学毕业证书》Bath一模一样原件
☞巴斯大学毕业证书[微/Q:865121257◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归&am ...
- 【爆料】-《堪培拉大学毕业证书》Canberra一模一样原件
☞堪培拉大学毕业证书[微/Q:2544033233◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归& ...
- js获取数组中最大值和最小值
var max = Math.max.apply(null, 数组); 获取最大值 var min = Math.min.apply(null, 数组);获取最小值 一句话获取数组中最大的数,最小数