一、前言

由于疫情原因,让我开始了以博客的方式来学习和分享技术(持续分享的过程也是自己学习成长的过程),同时也让更多的初学者学习到相关知识,如果我的文章中有分析不到位的地方,还请大家多多指教;以后我会持续更新我的文章,望大家多多支持和关注。

上几篇文章主要分享了IdentityServer4在Asp.Net Core 3.x 中的应用,在上面的几篇分享中有一部分博友问了我这么一个问题"他通过IdentityServer4 来搭建授权中心网关服务,怎么才能在访问受保护的Api资源中获取到用户的相关的身份信息呢?"。

那这篇文章主要来分享认证过程中的一个重要组成部分Claim,在开始之前强烈建议还没看过我写的 IdentityServer4 系列文章的同学先看一下,下面几篇文章中以架构思维带大家进入IdentityServer4 的世界

二、Claim 是什么

Claim

Claim 我的理解是一个声明,存储着一个键值对的关系,就相当于身份证中的 姓名:特朗普 , 性别:男等等身份证的系列元素,每一个项都是一个键值,我们看看Claim主要代码

public class Claim
{
public string ClaimType { get; set; } public string ClaimValue { get; set; }
}

代码中主要核心两个属性 ClaimTypeClaimValue;ClaimType 就是Key,ClaimValue就代表一个Value。这样的话,刚好可以存储一个键值对。这时候姓名:特朗普是不是就可以存进去了。

同时微软也提供了默认的ClaimType,部分默认的如下图:



Claim 差不多已经介绍完毕,相对比较简单清晰,Claim可以说是身份的最小单元的声明(身份单元)。

ClaimsIdentity

我们先来看看ClaimsIdentity的部分代码,代码如下:

public class ClaimsIdentity:IIdentity
{
public ClaimsIdentity(IEnumerable<Claim> claims){} //名字这么重要,当然不能让别人随便改啊,所以我不许 set,除了我儿子跟我姓,所以是 virtual 的
public virtual string Name { get; }
public string Label { get; set; } //身份单元集合
public virtual IEnumerable<Claim> Claims { get; } //这是我的证件类型,也很重要,同样不许 set
public virtual string AuthenticationType { get; } public virtual void AddClaim(Claim claim); public virtual void RemoveClaim(Claim claim); public virtual void FindClaim(Claim claim);
}

从代码中可以看到有一个Claims 属性,是一个集合,看到这里是不是可以把我们的身份证给联想进去呢?我们每个人都有一个“身份证”(ClaimIdentity),身份证中包含了多个“身份单元”(Claim)等信息。

从代码中还有一个特别重要的属性AuthenticationType 翻译成认证类型,这里也就相当于证件类型,比如身份证,它的证件类型就是"身份证",护照证机的证机类型就是"护照"。

同时ClaimsIdentity继承了IIdentity抽象接口,我们再来看看这个抽象接口的代码:

// 定义证件对象的基本功能。
public interface IIdentity
{
//证件名称
string Name { get; } // 用于标识证件的载体类型。
string AuthenticationType { get; } //是否是合法的证件。
bool IsAuthenticated { get; }
}

到这里ClaimsIdentity介绍的差不多了,ClaimsIdentity就相当于是身份证、或者护照之类的东西,一个能够证明身份的证件。

ClaimsPrincipal

一个人有了身份,就会有多重身份,比如你即是司机校长公司老板等等,那你就会有驾驶证教师证公司的营业执照等证件。那这些证件需要一个载体去容纳,那ClaimsPrincipal这个就相当于是这些证件的载体,我们来看看它的部分核心代码:

    public class ClaimsPrincipal : IPrincipal
{ public ClaimsPrincipal(); public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities); public ClaimsPrincipal(IIdentity identity); public ClaimsPrincipal(IPrincipal principal); public virtual IIdentity Identity { get; } public virtual IEnumerable<ClaimsIdentity> Identities { get; } //把证件添加到载体中
public virtual void AddIdentities(IEnumerable<ClaimsIdentity> identities);
//把证件添加到载体中
public virtual void AddIdentity(ClaimsIdentity identity); //以下都是从载体中获取证件等操作
public virtual IEnumerable<Claim> FindAll(Predicate<Claim> match); public virtual IEnumerable<Claim> FindAll(string type); public virtual Claim FindFirst(string type); public virtual Claim FindFirst(Predicate<Claim> match); public virtual bool HasClaim(Predicate<Claim> match); //是否属于某个角色
public virtual bool IsInRole(string role);
}

ClaimsPrincipal 介绍完了,我这里把ClaimsPrincipal 它叫证件的容器载体

我们已经知道了 “身份单元(Claims)” , “身份证(ClaimsIdentity)” , “证件容器载体(ClaimsPrincipal)”这三者的关系。

我们简单的来看下身份认证携带信息的简化的流程图:



好了,这里Claim相关概念理清楚了,这里也需要感谢下 微软MVP大佬@Savorboard 的文章https://www.cnblogs.com/savorboard/p/aspnetcore-identity.html 让我理清楚了这些关系!

三、实战

我这里继续我上几篇文章的代码基础上编写,需要代码的可以访问 https://github.com/a312586670/IdentityServerDemo 代码会跟着博客同步更新。

上几篇文章中解决方案中已经创建了如下三个项目:

  • Jlion.NetCore.Identity :Identity公共基础类库
  • Jlion.NetCore.Identity.Service : Ids4授权服务,也是上几篇文章中说的授权中心服务简单版本
  • Jlion.NetCore.Identity.UserApiService :用户业务网关(受保护的资源)

授权中心(Ids4授权服务)

Jlion.NetCore.Identity.Service

我们先在授权中心(ids4)服务中验证用户的代码中添加用户的相关Claims,核心代码如下:

不熟悉的请先移步

Asp.Net Core 中IdentityServer4 授权中心之应用实战 这篇文章

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
var userName = context.UserName;
var password = context.Password; //验证用户,这么可以到数据库里面验证用户名和密码是否正确
var claimList = await ValidateUserAsync(userName, password); // 验证账号
context.Result = new GrantValidationResult
(
subject: userName,
authenticationMethod: "custom",
claims: claimList.ToArray()
);
}
catch (Exception ex)
{
//验证异常结果
context.Result = new GrantValidationResult()
{
IsError = true,
Error = ex.Message
};
}
} #region Private Method
/// <summary>
/// 验证用户
/// </summary>
/// <param name="loginName"></param>
/// <param name="password"></param>
/// <returns></returns>
private async Task<List<Claim>> ValidateUserAsync(string loginName, string password)
{
//TODO 这里可以通过用户名和密码到数据库中去验证是否存在,
// 以及角色相关信息,我这里还是使用内存中已经存在的用户和密码
var user = OAuthMemoryData.GetTestUsers(); if (user == null)
throw new Exception("登录失败,用户名和密码不正确");
//我这里为了测试,简单的硬编码,生产环境可以通过数据库中读取到相关信息构造`Claim`(**身份单元**)
return new List<Claim>()
{
new Claim(ClaimTypes.Name, $"{loginName}"),
new Claim(EnumUserClaim.DisplayName.ToString(),"测试用户"),
new Claim(EnumUserClaim.UserId.ToString(),"10001"),
new Claim(EnumUserClaim.MerchantId.ToString(),"000100001"),
};
}
#endregion
}

现在,多个Claim已经构建完成,多个Claim构建出了一个用户身份,它们都属于即将登录的用户所拥有的身份单元,接下来我们还需要实现IProfileService抽象接口,

代码如下:

public class UserProfileService : IProfileService
{
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{ var claims = context.Subject.Claims.ToList();
// 把认证通过的用户身份
context.IssuedClaims = claims.ToList();
}
catch { }
} /// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}

GetProfileDataAsync主要为用户加载Claims,实现该代码并且通过AddProfileService<T>()方法添加到DI中,才能在API资源中获取到用户的身份信息,代码如下:

  public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(); #region 数据库存储方式
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
//.AddInMemoryClients(OAuthMemoryData.GetClients())
.AddClientStore<ClientStore>()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddExtensionGrantValidator<WeiXinOpenGrantValidator>()//添加微信端自定义方式的验证(上篇自定义授权方式的代码)
.AddProfileService<UserProfileService>(); #endregion }

Api资源(受保护的资源)

Jlion.NetCore.Identity.UserApiService

我们先来创建UserIdentityExtension扩展类,代码如下:

 public static class UserIdentityExtension
{
/// <summary>
/// 获得用户的Name
/// </summary>
/// <param name="this"></param>
/// <returns></returns>
public static string Name(this ClaimsPrincipal @this)
{
return @this?.Identity?.Name;
} /// <summary>
/// 获得DisplayName
/// </summary>
/// <param name="this"></param>
/// <returns></returns>
public static string DisplayName(this ClaimsPrincipal @this)
{
var value = @this?.Claims?.FirstOrDefault(oo => oo.Type == EnumUserClaim.DisplayName.ToString())?.Value;
return value;
} public static string UserId(this ClaimsPrincipal @this)
{
return @this?.Claims?.FirstOrDefault(oo => oo.Type == EnumUserClaim.UserId.ToString())?.Value;
} public static string MerchantId(this ClaimsPrincipal @this)
{
return @this?.Claims?.FirstOrDefault(oo => oo.Type == EnumUserClaim.MerchantId.ToString())?.Value;
}
}

再在原来的代码基础上新增UserController控制器,代码如下:

[Authorize]
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{ private readonly ILogger<UserController> _logger; public UserController(ILogger<UserController> logger)
{
_logger = logger;
}
}

UserController控制器已经创建完了,继承了ControllerBase基类,我们来看看ControllerBase包含了哪些信息,核心的代码如下:

/// <summary>
/// A base class for an MVC controller without view support.
/// </summary>
[Controller]
public abstract class ControllerBase
{
//通过请求上下文中获得User(证件载体容器)
public ClaimsPrincipal User => HttpContext?.User; //其他核心代码没有贴出来,具体的可以看官方源代码
}

看了源代码,我们是不是可以考虑使用User来获取身份证件中的某些身份元件呢?,在UserController添加获取用户信息的接口,完整代码如下:

[Authorize]
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{ private readonly ILogger<UserController> _logger; public UserController(ILogger<UserController> logger)
{
_logger = logger;
} [HttpGet]
public async Task<object> Get()
{
//通过ClaimsPrincipal(证件容器载体)获得某些证件的身份元件
var userId = User.UserId();
return new
{
name = User.Name(),
userId = userId,
displayName = User.DisplayName(),
merchantId = User.MerchantId(),
};
}
}

好了,代码已经构建完成!!!

现在我把两个服务通过命令行启动起来.

Jlion.NetCore.Identity.Service启动如下图:



我这里还是以"http://localhost:5000" 地址启动

Jlion.NetCore.Identity.UserApiService 启动如下图:



这里以"http://localhost:5001"地址启动

现在我们通过postman 访问ids4服务器获得accesstoken 如下图:



获取access_token 成功,我再携带access_token访问 用户业务网关,如下图:

成功获取到用户身份信息 Claims相关信息。

结论:ids4授权服务中构建用户身份信息(Claim)通过身份容器载体ClaimsPrincipal载入(具体载入到哪里?是怎么携带到Api资源网关中的?下篇文章再来分享具体的原理和流程);再经过受保护的Api资源网关中通过ClaimsPrincipal身份容器载体获得当前用户的相关信息后就可以做一些基于角色授权业务相关的事情。

博客系列文章源代码地址:https://github.com/a312586670/IdentityServerDemo

参考文章:

https://www.cnblogs.com/savorboard/p/aspnetcore-identity.html

Asp.Net Core 中IdentityServer4 实战之 Claim详解的更多相关文章

  1. Asp.Net Core 中IdentityServer4 实战之角色授权详解

    一.前言 前几篇文章分享了IdentityServer4密码模式的基本授权及自定义授权等方式,最近由于改造一个网关服务,用到了IdentityServer4的授权,改造过程中发现比较适合基于Role角 ...

  2. Asp.Net Core 中IdentityServer4 授权中心之应用实战

    一.前言 查阅了大多数相关资料,查阅到的IdentityServer4 的相关文章大多是比较简单并且多是翻译官网的文档编写的,我这里在 Asp.Net Core 中IdentityServer4 的应 ...

  3. Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式

    一.前言 上一篇我分享了一篇关于 Asp.Net Core 中IdentityServer4 授权中心之应用实战 的文章,其中有不少博友给我提了问题,其中有一个博友问我的一个场景,我给他解答的还不够完 ...

  4. Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用

    一.前言 上面分享了IdentityServer4 两篇系列文章,核心主题主要是密码授权模式及自定义授权模式,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享IdentityServer4的授权 ...

  5. ASP.NET Core MVC 源码学习:详解 Action 的激活

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  6. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  7. DevExpress ASP.NET Core Controls v18.2新功能详解

    行业领先的.NET界面控件2018年第二次重大更新——DevExpress v18.2日前正式发布,本站将以连载的形式为大家介绍新版本新功能.本文将介绍了DevExpress ASP.NET Core ...

  8. ASP.NET Core托管运行Quartz.NET作业调度详解

    Quartz.NET这么NB的作业调度系统,不会还行?   今天介绍一下Quartz.NET的托管运行,官网传送门. 一.前言 Quartz.NET,按官网上的说法,是一款功能齐全的任务调度系统,从小 ...

  9. IdentityServer4实战 - JWT Issuer 详解

    一.前言 本文为系列补坑之作,拖了许久决定先把坑填完. 下文演示所用代码采用的 IdentityServer4 版本为 2.3.0,由于时间推移可能以后的版本会有一些改动,请参考查看,文末附上Demo ...

随机推荐

  1. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  2. <SCOI2005>互不侵犯の思路

    日常玄学dp #include<cstdio> #include<cstring> #include<iostream> #include<algorithm ...

  3. 吴裕雄--天生自然KITTEN编程:青蛙答题过河

  4. 软件测试人必备的 Python 知识图

    之前发过蛮多不少关于 Python 学习的文章,收到大家不少的好评,不过大家也有许多困惑: 现在测试不好做,是不是真的该重新去学一门热门的语言? 入门 Python 该学哪些知识点?该看哪些书? 可以 ...

  5. Android 绘制中国地图

    最近的版本有这样一个需求: 有 3 个要素: 中国地图 高亮省区 中心显示数字 面对这样一个需求,该如何实现呢? 高德地图 因为项目是基于高德地图来做的,所以很自然而然的想到了高德.但是当查阅高德地图 ...

  6. 基于USB接口芯片CH372的人机接口设备设计与实现(转)

    摘 要: 基于一种新型USB 总线接口芯片CH372,设计出一种人机接口设备-USB 鼠标.阐述了CH372 的工作原理和特点,给出了系统的硬件电路图:在软件设计中,分析了HID 类设备描述符枚举过程 ...

  7. 用jekyll和github把网站建起来!

    先把这些天学习的用jekyll在github上搭建网站的步骤记录下来,留作参考. #安装jekyll 确定系统安装 Git, Ruby, RubyGems, Nodejs, Python2.7. 如何 ...

  8. python通用读取vcf文件的类(可以直接复制粘贴使用)

    前言   处理vcf文件的时候,需要多种切割,正则匹配,如果要自己写其实会比较麻烦,并且每次还得根据vcf文件格式或者需要读取的值不同要修改相应的代码.因此很多人会选择一些python的vcf的库,但 ...

  9. Flutter调研(2)-Flutter从安装到运行成功的一些坑

    工作需要,因客户端有部分页面要使用flutter编写,需要QA了解一下flutter相关知识,因此,做了flutter调研,包含安装,基础知识与demo编写,第二部分是安装与环境配置. —— 在mac ...

  10. XML转换

    找到两个不错的c#的关于XML转string和将string格式化XML输出 感谢以下两位的分享: [string格式化XML输出]http://blog.csdn.net/a497785609/ar ...