Netcore认证授权与IdentityServer4(3)IdentityServer4详解


主要内容

  1. OAuth2.0&Token
  2. IdentityServer4-四模式实现
  3. 授权策略和扩展
  4. OIDC-Hybrid

OAuth 2.0

什么是OAuth 2.0?

  • 授权机制,是一种规范/委托协议,制定了授权流程;它可以让那些控制资源的人允许某个应用以代表他们来访问他们控制的资源,值得注意的是,代表这些人,而不是假冒或模仿这些人。这个应用从资源的所有者那里获得到授权(Authorization)和Access Token,随后就可以使用这个access token来进行访问资源。
  • 解决授权问题,大家都遵守,才好沟通。
  • OAuth 2.0 授权Authorization,你能干什么;OIDC 身份认证,Authentication 你是谁;

Token

为什么需要授权?
送外卖的故事:
美团-饿了么-蜂鸟-跑腿小递
很多快递要进小区(假设能进来)
小区有密码锁—能给密码吗–不行

假设每次进来都打电话—确认了—放行—每次都要找我
假设弄个临时密码(有效期短点)—假如不再点饿了么—取消密码—大家都失效
一人一个临时密码—临时密码找我拿的—声明自己的身份—我确认了—给你个东西(临时密码/Token)—然后你就能进来—下次你还能进来

授权服务器做中介流程图:

这里的token(带点信息)使用过程 就是授权机制

  • 数据所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。
  • 系统从而产生一个短期的进入令牌(token),用来代替密码,
  • 供第三方应用使用。
  • 规范了下授权的流程

Token VS 密码

  • 都能进入系统,丢失后都是有风险的
  • Token短期的,密码是长期
  • 令牌可以取消,密码只能修改(影响全部)
  • Token可以控制权限,scope
  • OAuth,就是Token比密码安全

OAuth2.0 四种授权

  • 客户端凭证(client credentials)
  • 密码式(password)
  • 隐藏式(implicit)
  • 授权码(authorization-code)

    以用户访问豆瓣为例,需用通过QQ授权服务来实现登录,使用code模式:

OAuth2.0 中的几个关键概念

端点 Endpoint

Authorization ENdpoint 授权端点
Token Endpoint, Token端点

范围 Scope

代表资源所有者在被保护资源那里的一些权限。

Access Token

  • 有时候就叫Token
  • 用来访问被保护资源的凭据
  • 代表了给客户端颁发的授权,也就是委托给客户端的权限
  • 描述出Scope,有效期

Refresh Token

  • 用来获取Access Token的凭据
  • 由Authorization Server 颁发给客户端应用的
  • 可选
  • 具备让客户端应用逐渐降低党文权限的能力


OAuth2.0 发生错误

  • error
  • error_description
  • error_uri
  • state

下面是几种错误类型:

  • invalid_request
  • invalid_client(401)
  • invalid_grant
  • unauthorized_client
  • unsupported_grant_type
  • invalid_scope

OpenID Connect

OAuth 2.0不是身份认证协议
什么是身份认证协议?
它可以告诉应用程序当前的用户是谁,还有这些用户是否正在使用你的应用程序,它是一种安全架构,它可以告诉你用户使他们所声明的身份;通常,是通过提供一套安全凭据(例如用户名和密码)给应用程序来证明这一点。

身份认证 VS 授权

  • 身份认证 Authentication
  • 授权 Authorization

引用《OAuth 2.0 in Action》里面的一个比喻,把身份认证看作是软糖,而授权是巧克力。这两种东西感觉略有相似,但是本质上确实截然不同的:巧克力是一种原料,而软糖是一种糖果。可以使用巧克力作为主要预料做出巧克力口味的糖果,但是巧克力和软糖不是等价的。

尽管巧克力可以单独作为一种最终产品,但是在这个比喻里面巧克力是一种非常有用的原料,它极具多样性,可以用来做蛋糕,冰淇淋,雪糕等。

要制作巧克力软糖,也就是需要一个基于OAuth 2.0的身份认证协议。而OpenId Connect就是这样一个开放标准,它可以工作于不同的身份和供应商之间。OpenId Connect基于OAuth 2.0,在此之上,它添加了一些组件来提供身份认证能力。

OpenID Connect 的官方定义是: OpenID Connect 是建立在OAuth2.0 协议上的一个简单的身份标识层,OpenID Connect兼容OAuth 2.0.

OAuth 2.0与身份认证协议的角色映射如下:

为什么不使用OAuth 2.0里面的Access Token来解决这个问题呢?

  • Access Token不含有身份认证的信息
  • 生命周期可能很长,用户离开了,它仍有效
  • Access Token可能被其他客户端借用
  • Access Token不是为客户端准备的,它对客户端不透明,但是客户端可以从Access Token里得到一些用户信息。Access Token的真正目的是保护资源。

OIDC的具体内容

  • ID Token
  • UserInfo端点
  • 提供一组表示身份的scopes和claims: profile email address phone

OIDC的三个流程

  • Authorization Code Flow
  • Implicit Flow
  • Hybrid Flow

IdentityServer4

IdentityServer4: ASP.NET CORE量身定制,实现了OpenID Connect和OAuth2.0协议,是认证授权中间件。
IdentityServer4有两种token,一种就是JWT Token,另一种是Reference Token,中心化的Token,需要每次校验都是访问鉴权授权中心的,一般不怎么使用。

创建鉴权授权中心

Step1 下载IdentityServer4的模板

参考Id4官方文档:https://identityserver4.readthedocs.io/en/latest/quickstarts/0_overview.html
首先使用命令安装模板:
dotnet new -i IdentityServer4.Templates

Step2 创建一个带UI的Id4项目

这里我们创建一个数据存放在内存中的Id4模板,命名为Chaoqiang.Demo.Id4
使用命令创建项目:
dotnet new is4inmem --name Chaoqiang.Demo.Id4
使用命令创建解决方案Chaoqiang.Demo并添加项目Chaoqiang.Demo.Id4到该解决方案中:
dotnet new sln -n Chaoqiang.Demo
dotnet sln add .\Chaoqiang.Demo.Id4\Chaoqiang.Demo.Id4.csproj

Step3 启动Id4项目

启动该项目如下图所示:

Step4 结合代码理解Id4用法


这里只以startup为例描述id4这个中间件是如何被使用的,这其中还有很多放在内存中的资源和客户端,可以自行查看。

OAuth 2.0 Client Credentials

先从最简单的客户端这种模式开始,不代表用户,也就没有用户信息。

Id4改造

首先我们对Id4项目进行一些改造,添加我们自己创建的Client到内存中;

创建客户端

首先,我们创建一个01ConsoleClient的控制台客户端应用,在控制台中使用客户端模式(client_credentials)这种方式来访问Id4。
在这个控制台中,我们会去做这几件事,第一,查看Id4的document信息;第二,根据对应信息,获取access token;第三,访问带验证的API资源(需要先创建API);

解析Id4的document

控制台需要使用Nuget package IdentityModel,这个Model的作用就是可以方便的解析Id4的document.

这里我们可以看一下Id4的文件解析格式,访问如下地址:
http://localhost:5000/.well-known/openid-configuration
这个openid-configuration的文件中包含了很多我们需要的信息:

  • authorization_endpoint: “http://localhost:5000/connect/authorize“,
  • token_endpoint: 获取token的地址 “http://localhost:5000/connect/token“,
  • userinfo_endpoint: 获取用户信息的地址”http://localhost:5000/connect/userinfo“,
  • scopes_supported : “UserApi”, “offline_access”
  • claims_supported
  • grant_types_supported:”authorization_code”, “client_credentials”, “refresh_token”, “implicit”
  • response_types_supported: “code”, “token”, “id_token”, “id_token token”, “code id_token”, “code token”, “code id_token token”

同步地可以快速监视一下拿到的doc这个变量:

控制台获取access_token

在拿到id4的文档之后,要访问tokenpoint来获取access token,先关的代码片段如下:

var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
  Address = disco.TokenEndpoint,
  ClientId = "Chaoqiang.Demo.ClientCredential",
  ClientSecret = "chaoqiang123456",
  Scope = "UserApi"
});

if (tokenResponse.IsError)
{
  Console.WriteLine(tokenResponse.Error);
  return;
}

这里需要注意的是:控制台配置的ClientId和ClientSecret以及Scope都需要和Id4那边保持一致,这个tokenEndpoint的地址是:http://localhost:5000/connect/token;

这里还可以通过Postman来模拟一下获取token的过程,需要传递相关参数:

进一步地,假设这里我们想在控制台通过Auth的客户端模式访问用户信息,同时在Id4和控制台的scope都加上openid,如下:

这时发现 invaild scope报错, 此时这种Credentials的授权方式不代表任何用户,想一下,你拿着一个ClientId 想要访问Openid这是不合理的。

访问API资源

这里我们新建一个Net Core API资源,然后还需要在APIResource的Startup中加入Authentication的部分,进行一些改造,这里可以有两种做法:

  1. 第一种是安装这个Nuget package Microsoft.AspNetCore.Authentication.JwtBearer, 注意一下与Net Core的版本兼容。
public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
  services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
      options.Authority = "http://localhost:5000";
      options.RequireHttpsMetadata = false;
      options.Audience = "UserApi";
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
  }

  app.UseRouting();
  app.UseAuthentication();
  app.UseAuthorization();

  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllers();
  });
}

第二种是使用Id4的中间件验证包:IdentityServer4.AccessTokenValidation然后Authentication的配置就变成下面这样:

services.AddAuthentication("Bearer")
  .AddIdentityServerAuthentication(options =>
  {
    options.Authority = "http://localhost:5000";
    options.RequireHttpsMetadata = false;
    options.ApiName = "UserApi";
  });

接着,再增加一个Identity的controller,带上Authorize的标签,用于返回用户信息,如下:

[Route("identity")]
[ApiController]
[Authorize]
public class IdentityController : ControllerBase
{
  [HttpGet]
  public IActionResult Get()
  {
    return new JsonResult(
      from c in User.Claims select new {c.Type,c.Value});
  }
}

万事具备,现在可以在控制台中去call这个带有认证的API资源了:

当然这个过程,也可以用Postman来模拟一下:

这里我们再梳理一下整个访问的流程和涉及的站点:

  • 控制台,使用客户端模式
  • Id4,鉴权授权中心(5000端口)
  • API资源(8888端口)

我们可以通过Fiddler来查看一下 整个访问的过程:

还可以将得到的access token去JWT官网解析一下:

权限控制

进一步地,我们可以在API的上面增加一些授权策略,例如要求用户的邮箱是qq邮箱还是163邮箱;

services.AddAuthorization(options =>
{
  options.AddPolicy("eMailPolicy",
  policyBuilder => policyBuilder
  .RequireAssertion(context =>
  context.User.HasClaim(c => c.Type == "client_eMail")
  && context.User.Claims.First(c => c.Type.Equals("client_eMail")).Value.EndsWith("@163.com")));//Client
});

这样的话,与我们传入的Claim中的邮箱要求就不符合了;还要注意的是此处Type是client_eMail,要与Id4中的Claim保持一致;

小结一下: Client Credentials保护无人客户端并访问被保护资源

  • OAuth 2.0 Client Credentials
  • 客户端应用不代表用户,客户端应用本身就相当于是资源所有者
  • 通常用于机器对机器的通信
  • 客户端也需要身份认证的

OAuth 2.0 Password Grant

这里我们通过Postman快速看一下密码模式的特点:
首先,我们在Id4里的Authebntication中进行一些改造,将授权方式改为密码模式;

services.AddIdentityServer()
  .AddDeveloperSigningCredential()//默认的开发者证书               .AddInMemoryApiResources(PasswordInitConfig.GetApiResources())//API访问授权资源
  .AddInMemoryClients(PasswordInitConfig.GetClients())  //客户端
  .AddTestUsers(PasswordInitConfig.GetUsers());//添加用户

下面我们重点卡看一下相比于客户端模式,密码模式初始化数据上的不一样:

  • 新增了一个GetUsers, 增加了用户数据这部分;
  • 在客户端的信息里面,没有cliams,因为就算有也没法传过去,用户数据统一放在user claim里面了;
  • 在APISource中,新增了Role和email等claim,这是因为在授权策略里面会读取email来判断邮箱类型;
  • 值得注意的是,此时在用户信息里的claim和之前client里的claim的名字已经不一样了,例如client中是client_eMail,但是User中是eMail;
  • 另外,虽然不能在Id4的Client加入claims,但是可以在Client的scope可以加入一些Identity信息(用户信息),这样也能获取到,后看将通过实例看一下。

创建WPF客户端

这里将通过一个WPF客户端使用密码模式来访问Id4和API资源的例子详细剖析各个过程。
创建WPF客户端,引用Nuget package: IdentityModel
大概的布局如下,分为三步,首先获取Access Token,然后根据Access Token请求API资源,最后还可以验证一下请求id4中的Identity资源。

获取Access Token

WPF客户端带着用户密码向Id4请求Access Token:

通过access token解析出来的内容,我们可以对比一下:

  • Id4中的APIResource的Scope范围不一样,会导致最终获取到的token中的claim也不一样;
  • 当WPF使用的Scope是TestApi的时候,发现能获取到用户的eMail和role这两个信息,因为TestApi的APIResource中包含了这些Claim信息;


访问API资源

这里我们也对比一下,访问API资源的Identity,不同的API资源Scope,最终在被访问的API中拿到的Claim也是不一样的。例如这里的eMail,role, title等信息都是需要在API资源的Scope中增加的。
基于此,我们也可以对API进行一些授权策略的定制化,这里就不展开了,与上述客户端模式时的授权策略类似;

获取Identity资源

除了获取API Source中的资源,还可以获取Identity的资源,也就是用户的信息,这个同样可以通过更改客户端的scope来进行获取;

我们需要先添加Idnetity资源,然后才能扩大WPF客户端请求的scope,否则在请求access token时就会报invalid_token这样的错误:

增加IdentityResource,在Id4的Startup和InitConfig中进行一些改造:

#region 密码模式
services.AddIdentityServer()
  .AddDeveloperSigningCredential()//默认的开发者证书
  .AddInMemoryIdentityResources(PasswordInitConfig.GetIdentityResources())//添加Identity资源
  .AddInMemoryApiResources(PasswordInitConfig.GetApiResources())//API访问授权资源
  .AddInMemoryClients(PasswordInitConfig.GetClients())  //客户端
  .AddTestUsers(PasswordInitConfig.GetUsers());//添加用户
#endregion

对应的在WPF客户端中,可以扩大scope来请求access token了:

然后拿着请求到的token去访问Identity资源,即用户信息:

private async void RequestIdentityResource_ButtonClick(object sender, RoutedEventArgs e)
{
  // call Identity Resource from Identity Server
  var apiClient = new HttpClient();
  apiClient.SetBearerToken(_accessToken);

  var response = await apiClient.GetAsync(_disco.UserInfoEndpoint);
  if (!response.IsSuccessStatusCode)
  {
    MessageBox.Show(response.StatusCode.ToString());
  }
  else
  {
    var content = await response.Content.ReadAsStringAsync();
    IdentityResponseTextBlock.Text = content;
  }
}

验证一下拿到的用户信息:

小结:OAuth 2.0 Password Grant,用户需要把密码提供给第三方,资源所有者的密码凭证(例如用户名和密码)直接被用来请求Access Token,通常用于遗留的应用,资源所有者和客户端应用之间必须高度信任,其它授权方式不可用的时候才使用,尽量不用。

Implicit Flow 隐藏式

  • 浏览器模式—允许授权给浏览器
  • 用户访问A—没有token—A提供地址跳转B登录
  • –输入账号密码—授权—调回A的地址(url带token)–访问A时带上token
  • 保护了密码—暴露了token

下面开始配置,Id4中的Authentication进行改造:

#region 隐藏模式
services.AddIdentityServer()
  .AddDeveloperSigningCredential()//默认的开发者证书
  .AddInMemoryApiResources(ImplicitInitConfig.GetApiResources()) //API访问授权资源
  .AddInMemoryClients(ImplicitInitConfig.GetClients())//客户端
  .AddTestUsers(ImplicitInitConfig.GetUsers()); //添加用户
#endregion

Id4中客户端更改客户端授权方式:

public static IEnumerable<Client> GetClients()
{
  return new[]
  {
    new Client
    {
      ClientId = "Chaoqiang.Demo.ImplicitGrant",//客户端惟一标识
      ClientName="ApiClient for Implicit",
      ClientSecrets = new [] { new Secret("chaoqiang123456".Sha256()) },
      AllowedGrantTypes = GrantTypes.Implicit,//隐藏模式
      RedirectUris={"http://localhost:8888/Identity" },//可以多个,根据请求来的转发
      AllowedScopes = new [] { "UserApi","TestApi" },//允许访问的资源
      AllowAccessTokensViaBrowser=true//允许将token通过浏览器传递
    }
  };
}

这个时候请求token的地址是下面这种格式的:
隐藏模式:用户—应用A—腾讯授权中心
http://localhost:5000/connect/authorize?client_id=Chaoqiang.Demo.ImplicitGrant&redirect_uri=http://localhost:8888/Identity/identityToken&response_type=token&scope=UserApi
用户访问应用A—需要token—跳转到授权中心—应用A提供地址—然后用户向腾讯授权中心输入账号密码
http://localhost:8888/Identity/identityToken#access_token=eyJhbGciOiJSUzI1NiIsImtpZCI6InQtaG5LSXRlN1ZLUktrZXZNNzhPUlEiLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1OTIxMDY3NDUsImV4cCI6MTU5MjExMDM0NSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiVXNlckFwaSIsImNsaWVudF9pZCI6IkNoYW9xaWFuZy5EZW1vLkltcGxpY2l0R3JhbnQiLCJzdWIiOiIwIiwiYXV0aF90aW1lIjoxNTkyMTAzNTAwLCJpZHAiOiJsb2NhbCIsInJvbGUiOiJBZG1pbiIsImVNYWlsIjoiOTI1NjEwMDY5QHFxLmNvbSIsInNjb3BlIjpbIlVzZXJBcGkiXSwiYW1yIjpbInB3ZCJdfQ.W6zve3WoQc0Zu6Hz4Cs2FeH8I-oq0_pjSLBkifigbt5AKhUDXkGge0juqBbbRDvfeq99ZzHENEnro98lNULt1TvIPzZauzPokTv8bSnPl6GsAi9ctIy17eWTofdAsmGiUhoLxmnmA6umA5ejCxtqFSg_kAZDK6Yvf_KneAyH6mc_ZPd_97KWHTLE4u63mD2jL27xSroN7s6Gp8HPZcmkyF4GZbrcgUSCZiryIBl7oHKzpPebNDygV5m-BpJmnAQpaj8GfLJqj5eQKmtVbtrPO2bf8y-XFUSYss5vIkxYNcMDuUE1wOvjMDBahN3YzXtEcoIOjGRXH6t2JoNSUnLXsg&token_type=Bearer&expires_in=3600&scope=UserApi
到这里可以获取到token

紧接着,拿到token之后,我们就可以去访问identity这个带头认证的方法了,这里我们用Postman来模拟一下:

Implicit Flow的特点:

  • 用于“公共”客户端 例如angular单页应用程序
  • 最好是客户端应用可以直接从浏览器访问资源
  • 没有显式的客户端身份认证
  • token直接就暴露在浏览器地址上

这里我们可以通过一个使用Implicit Flow保护Angular (SPA)客户端的实例来演示这个详细的过程,主要涉及访问被保护的资源,刷新Access Token。
这里先挖个坑,后面结合openid再填!

Authorization Code Flow授权码模式

先授权码,再Token,保护密码-保护token;
拿到授权码之后就在后端去访问token了;

同样的,我们对Id4进行一些改造:

#region 授权码模式
  services.AddIdentityServer()
  .AddDeveloperSigningCredential()//默认的开发者证书 
  .AddInMemoryApiResources(CodeInitConfig.GetApiResources()) //API访问授权资源
  .AddInMemoryClients(CodeInitConfig.GetClients())//客户端
  .AddTestUsers(CodeInitConfig.GetUsers()); //添加用户
 #endregion

第一步,先获取授权码Code
授权码模式:用户—应用A—腾讯授权中心
http://localhost:5000/connect/authorize?client_id=Chaoqiang.Demo.AuthorizationCode&redirect_uri=http://localhost:8888/Identity/IdentityCode&response_type=code&scope=UserApi
用户访问应用A—需要token—跳转到授权中心—应用A提供地址—然后用户向腾讯授权中心输入账号密码
–返回Code–拿着Code+clientpassword通过后端去获取token
http://localhost:8888/Identity/IdentityCode?code=TS4O787i5FHLklTqVoINwB6FZCqXym9X4KmMLOURSM8&scope=UserApi
至此,我们就拿到了Code。

第二步,过后端去获取token
这个过程我们通过Postman来模拟一下:

第三步,拿着token去访问带有验证的API方法
拿到access token之后我们就可以”为所欲为“了,这时候就可以去访问API带有验证的法法了,这里的过程就不再赘述了,与上面相似。

OIDC OpenId Connect


OIDC(OpenID Connect)是在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证标准协议。OAuth2授权协议—解决了N多个快递员如何进小区的问题Access_Token—门卫不知道快递员是谁—没有用户信息(可以有,但是不关心)
OAuth—–>Access_Token
OIDC—–>Id_Token
一次请求,可以拿到2个token

  • Access_Token && Id_Token
  • OpenID Connect— 2个加起来(Access Token + ID Token)
  • Openid浏览器返回—混合式

客户端-密码式不能玩,OpenID是基于浏览器的,所以oidc是适合隐藏式和授权码模式的。也就是说Id_token的获取就是多一个返回信息,因为只能通过url回调。



Hybrid 混合模式


先从Id4的Startup配置开始,会发现这里新增加了IdentityResource,这在上面也提到过了,这里再单独拿出来说一下:

#region 混合模式
services.AddIdentityServer()
  .AddDeveloperSigningCredential()//默认的开发者证书 
  .AddInMemoryIdentityResources(HybridInitConfig.GetIdentityResources())//身份信息授权资源
  .AddInMemoryApiResources(HybridInitConfig.GetApiResources()) //API访问授权资源
  .AddInMemoryClients(HybridInitConfig.GetClients())//客户端
  .AddTestUsers(HybridInitConfig.GetUsers()); //添加用户
#endregion

我们重点关注一下GetIdentityResources和GetClients这里面的变化:
可以放入自定义的用户信息,也有一些定义好的用户信息scope。

/// <summary>
/// 用户信息,能返回哪些用户信息
/// </summary>
/// <returns></returns>
public static IEnumerable<IdentityResource> GetIdentityResources()
{
  return new IdentityResource[]
  {
    new IdentityResources.OpenId(),
    new IdentityResources.Profile(),//一堆默认属性
    new IdentityResource(
      "CustomIdentityResource",
      "This is Custom Model",
      new List<string>(){ "phonemodel","phoneprise", "eMail"})//自定义Id资源,植入claim
  };
}

当然这些资源都需要在用户的Claim里面加进去,后面才能读取到:
可以看到用户的Claim里面加了很多信息,例如Role,NickName,Name,prog,phonemodel等。

public static List<TestUser> GetUsers()
{
  return new List<TestUser>()
  {
    new TestUser()
    {
      Username="chaoqiang",
      Password="123456",
      SubjectId="0",

      Claims=new List<Claim>(){
        new Claim(IdentityModel.JwtClaimTypes.Role,"Admin"),
        new Claim(IdentityModel.JwtClaimTypes.NickName,"Chaoqiang"),
        new Claim(ClaimTypes.Name,"apiUser"),
        new Claim("eMail","925610069@qq.com"),
        new Claim("prog","正式项目"),
        new Claim("phonemodel","huawei"),
        new Claim("phoneprise","5000元"),
      }
    }
  };
}

还有一个是Client上的一些变化:需要把这些scope给暴露出去,客户端读取Id-Token时才能获取到。openid是必须加入的scope。

public static IEnumerable<Client> GetClients()
{
  return new[]
  {
    new Client
    {
      AlwaysIncludeUserClaimsInIdToken=true,
      AllowOfflineAccess = true,

      ClientId = "Chaoqiang.Demo.Hybrid",//客户端惟一标识
      ClientName="ApiClient for HyBrid",
      ClientSecrets = new [] { new Secret("chaoqiang123456".Sha256()) },
      AccessTokenLifetime=3600,//默认1小时
      AllowedGrantTypes = GrantTypes.Hybrid,//混合模式
      RedirectUris={"http://localhost:5726/Ids4/IndexCodeToken" },//可以多个
      AllowedScopes = new [] {
        "UserApi",
        "TestApi",//资源范围
        IdentityServerConstants.StandardScopes.OpenId,//Ids4:获取Id_token,必需加入"openid"
        IdentityServerConstants.StandardScopes.Profile,//用户信息范围
        "CustomIdentityResource"},
      AllowAccessTokensViaBrowser=true//允许将token通过浏览器传递
    }
  };
}

这个 时候请求的参数和之前有点不太一样了:

  • response_type 返回token还是id_token还是都返回,scope范围也变大了;

http://localhost:5000/connect/authorize?client_id=Chaoqiang.Demo.Hybrid&redirect_uri=http://localhost:8888/Identity/IdentityCodeToken&response_type=code%20token%20id_token&scope=UserApi%20openid%20CustomIdentityResource&response_model=fragment&nonce=12345

拿着这个地址直接去请求就可以获取相应的response_type,会让你输入用户密码:

返回的这个结果里面有几个token,可以拿来解析一下:

http://localhost:8888/Identity/IdentityCodeToken#**code**=Hvp8az0al1s4fRi--XoLEC8rPrcHubHaeJ9OET3z_l4&**id_token**=eyJhbGciOiJSUzI1NiIsImtpZCI6InQtaG5LSXRlN1ZLUktrZXZNNzhPUlEiLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1OTIxMTY3NTEsImV4cCI6MTU5MjExNzA1MSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiQ2hhb3FpYW5nLkRlbW8uSHlicmlkIiwibm9uY2UiOiIxMjM0NSIsImlhdCI6MTU5MjExNjc1MSwiYXRfaGFzaCI6IkhheEpHZjNWRi1oUW9CQkRIT2xqLVEiLCJjX2hhc2giOiJYQ2RzX0JJZ1V2TTBXUk01ZkgtaGR3Iiwic2lkIjoidXYzVERUa3oyLUdsSWhlR0cyaG9pdyIsInN1YiI6IjAiLCJhdXRoX3RpbWUiOjE1OTIxMTE1MDUsImlkcCI6ImxvY2FsIiwiZU1haWwiOiI5MjU2MTAwNjlAcXEuY29tIiwicGhvbmVtb2RlbCI6Imh1YXdlaSIsInBob25lcHJpc2UiOiI1MDAw5YWDIiwiYW1yIjpbInB3ZCJdfQ.AB0dxV3GHweMP-A4TVzqK7x_2zeEcxmjcC8NBYXf9hcPGMMq5ea2i0sgU1JiGrWIs_w_jPfFb5UUPFasfa0RhKdgF9LrkJI7zL3whYdUWCVOlsVvno2zLw7B_rrn-lUOVrdHbMXi9SaXiawct9VaLN7cx-KuvbWm18VjNwO90-bcJYdJ1L3umM_802CtjatmS61Jm6RHSgxFAqgmbRLSO4fEMu52roaFw2uXr_vr2YX5Hte1HW3g2pDNJ0K_Yc54tI3LzakxMMq88bblnuXJcVZOU0MUtxTo8Airs_2nyLdVAtxDZ8Sp0FIGDnzVL8P0KwdXcESCuh3y2_LHbI1yxQ& access_token=eyJhbGciOiJSUzI1NiIsImtpZCI6InQtaG5LSXRlN1ZLUktrZXZNNzhPUlEiLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1OTIxMTY3NTEsImV4cCI6MTU5MjEyMDM1MSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiVXNlckFwaSIsImNsaWVudF9pZCI6IkNoYW9xaWFuZy5EZW1vLkh5YnJpZCIsInN1YiI6IjAiLCJhdXRoX3RpbWUiOjE1OTIxMTE1MDUsImlkcCI6ImxvY2FsIiwicm9sZSI6IkFkbWluIiwiZU1haWwiOiI5MjU2MTAwNjlAcXEuY29tIiwic2NvcGUiOlsib3BlbmlkIiwiQ3VzdG9tSWRlbnRpdHlSZXNvdXJjZSIsIlVzZXJBcGkiXSwiYW1yIjpbInB3ZCJdfQ.MslmyGlDkVRwkG3XE1DGnXerUUsGhVb0f1KyOU3LU7lSv6mM5kj8rDh84upDBxEeIcQ3JGSl9Kd5ZgrWB5lV8OsKEteY3YUciFMAZ1L_gvfLhWKJeiPe669-40_JMSxmwX_6RWdwCURvqQ4bUeaGrYXzhGcnmkAAFO1MjWBEYFyMnIDSc5xuuqlpXRRppLrylWlHHoAY6oQ2ya0q7GyUdmCA5ITAQxOkxQWzgpDidvSHttX2QBbVCcKXAGnc-gKDuecixU-S1aGT6GC1nM1aREFQJcm9nbazenB25uOIl7XeAd81xpzz4BEz0SiRDHBU7WFW6DJrdB-GHtGljYScbg&token_type=Bearer&expires_in=3600&scope=openid%20CustomIdentityResource%20UserApi&session_state=_bKodowTCym88ixEUSrVjvykFZFMAFPVtBgiAI9xsw4.oGtn1UvHY5iEAcBoPB56GA


与之前的类似,拿着access token可以访问带有验证的API的方法;
对于id_token的用法,我们可以用来获取用户信息,主要用于单点登录,我们会把id_token往cookie里面塞,每次请求的时候带上。那么后端就可以解析这个id_token,然后获得用户信息。

[HttpGet]
public IActionResult Get()
{
  //解析id_token
  string id_token = base.HttpContext.Request.Cookies["id_token"];
  var token_parts = id_token.Split('.');
  var header = Encoding.UTF8.GetString(Base64Url.Decode(token_parts[0]));
  var claims = Encoding.UTF8.GetString(Base64Url.Decode(token_parts[1]));
  var sign = Encoding.UTF8.GetString(Base64Url.Decode(token_parts[2]));
  Console.WriteLine(header);
  Console.WriteLine(claims);
  Console.WriteLine(sign);

  return new JsonResult(
    from c in User.Claims select new {c.Type,c.Value});
  }

这里我们用postman来模拟,在cookie中加上id_token,然后访问时读取id_token中的内容:

总结一下:混合模式一共有如下三种response的格式:

  • response_type=code id_token
  • response_type=code token
  • response_type=code id_token token



至此,我们把几种模式都看了一遍,基本上对于OAuth和OpenId的理解也算是入门了,后面有时间来到时候考虑做几个基于OpenId配置的实例,还有将内存这种方式变为数据库存储的方式,争取把坑填上。


文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
Docker-Crash-Tutorial Docker-Crash-Tutorial
主要内容 Docker VS VirtualMachine centOS7下Docker基本操作 Asp.Net Core+Docker Docker部署集群负载均衡 Docker-compose docker-machine dock
下一篇 
Netcore认证授权与IdentityServer4(2)JWT Netcore认证授权与IdentityServer4(2)JWT
主要概要这个文章主要来聊一聊Net Core中的认证与授权,以及Net Core生态中非常火热的IdentityServer4这个组件的使用,主要内容如下: 鉴权授权流程变化 源码解读鉴权 源码解读多授权策略 JWT和Identity
  目录