前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Open ID Connect(OIDC)在 ASP.NET Core中的应用

Open ID Connect(OIDC)在 ASP.NET Core中的应用

作者头像
用户1153966
发布2018-03-14 13:30:59
2.5K0
发布2018-03-14 13:30:59
举报
文章被收录于专栏:jessetalks

我们在《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

代码语言:javascript
复制
GET /connect/authorize?response_type=code&client_id=postman&state=xyz&scope=api1
        &redirect_uri=http://localhost:5001/oauth2/callback

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

代码语言:javascript
复制
https://localhost:5001/oauth2/callback?code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c
&scope=api1
&state=111271607

第三步,用code换取access token

代码语言:javascript
复制
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

代码语言:javascript
复制
{
    "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即为用户的唯一识别号
代码语言:javascript
复制
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

代码语言:javascript
复制
https://localhost:5001/oauth2/callback#
code=c5eaaaca8d4538f69f670a900d7a4fa1d1300b26ec67fba2f84129f0ab4ffa35
&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjVjMzA5ZGIwYTE2OGEwOTgGtpbj0GVXNnkKhGdrzA
&scope=openid%20profile%20api1&state=111271607

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

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

代码语言:javascript
复制
Get http://localhost:5000/connect/userinfo
Authorization Bearer access_token

返回的用户信息

代码语言:javascript
复制
{
    "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引用
  • 依赖注入初始化
代码语言:javascript
复制
services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddTestUsers(Config.GetTestUsers());
  • 中间件添加
代码语言:javascript
复制
app.UseIdentityServer();
  • 配置

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

api resources

代码语言:javascript
复制
public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1", "API Application"){
                    UserClaims = { "role", JwtClaimTypes.Role }
                }
            };
        }

identity resources

代码语言:javascript
复制
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种进行了组保。

代码语言:javascript
复制
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

代码语言:javascript
复制
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的客户端。我们需要在依赖注入中添加以下配置:

代码语言:javascript
复制
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的帮助,完成这一步只需要一句简单的配置即可:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 OIDC
  • OIDC对oAuth进行了哪些扩展?
  • Identity Server4提供的OIDC认证服务
  • ASP.NET Core的权限体系中的OIDC认证框架
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档