一.前言

本文为系列补坑之作,拖了许久决定先把坑填完。

下文演示所用代码采用的 IdentityServer4 版本为 2.3.0,由于时间推移可能以后的版本会有一些改动,请参考查看,文末附上Demo代码。

本文所诉Token如无特殊说明皆为 JWT。

众所周知 JWT Token 由三部分组成,第一部分 Header,包含 keyid、签名算法、Token类型;第二部分 Payload 包含 Token 的信息主体,如授权时间、过期时间、颁发者、身份唯一标识等等;第三部分是Token的签名。我们对一个 Token 进行解码,观察其中 Payload 部分,你将会发现一个 "iss" 字段,那么它代表什么呢,它又有什么作用呢,请看后文分解。

二. Issuer 的前世今生

iss 是 OpenId Connect(后文简称OIDC)协议中定义的一个字段,其全称为 “Issuer Identifier”,中文意思就是:颁发者身份标识,表示 Token 颁发者的唯一标识,一般是一个 http(s) url,如 https://www.baidu.com

在 Token 的验证过程中,会将它作为验证的一个阶段,如无法匹配将会造成验证失败,最后返回 HTTP 401。

三. Issuer 的验证流程分析

JWT的验证是去中心化的验证,实际这个验证过程是发生在API资源的,除了必要的从 IdentityServer4 获取元数据(获取后会缓存,不用重复获取)比如获取公钥用于验证签名,是不会再去交互的(详细介绍:https://www.cnblogs.com/stulzq/p/9226059.html)。那么我们就从 API 资源作为入口开始分析。

我们在 API 资源的配置认证的代码如下:

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters(); services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
// IdentityServer4 地址
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
} public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc();
}
}
}

一个携带 Token 的请求从认证中间件到最终验证 Issuer 的逻辑如下图(懒得画流程图了,直接做个gif)。

(新窗口打开查看大图)

最终验证 Issuer 的代码:

public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
//最终进入 验证Issuer逻辑
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters)); if (!validationParameters.ValidateIssuer)
{
LogHelper.LogInformation(LogMessages.IDX10235);
return issuer;
} if (validationParameters.IssuerValidator != null)
return validationParameters.IssuerValidator(issuer, securityToken, validationParameters); if (string.IsNullOrWhiteSpace(issuer))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211)
{ InvalidIssuer = issuer }); // Throw if all possible places to validate against are null or empty
if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) && (validationParameters.ValidIssuers == null))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204)
{ InvalidIssuer = issuer }); if (string.Equals(validationParameters.ValidIssuer, issuer, StringComparison.Ordinal))
{
LogHelper.LogInformation(LogMessages.IDX10236, issuer);
return issuer;
} if (null != validationParameters.ValidIssuers)
{
foreach (string str in validationParameters.ValidIssuers)
{
if (string.IsNullOrEmpty(str))
{
LogHelper.LogInformation(LogMessages.IDX10235);
continue;
} if (string.Equals(str, issuer, StringComparison.Ordinal))
{
LogHelper.LogInformation(LogMessages.IDX10236, issuer);
return issuer;
}
}
} throw LogHelper.LogExceptionMessage(
new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX10205, issuer, (validationParameters.ValidIssuer ?? "null"), Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)))
{ InvalidIssuer = issuer });
}

由源码分析可以获得几个结论:

1.验证 Token 必定会验证 Issuer,如果 Issuer 验证失败,那么表示则整个 Token 的验证结果就是失败。

  1. Issuer 默认从 IdentityServer4 的 Discovery Endpoint(/.well-known/openid-configuration)获取Issuer

3.Issuer 可以自定义,并且可以设置一个列表,如果手动设置了会覆盖默认值

4.Issuer 验证逻辑默认只验证是否相等,即 Token 携带的 Issuer 是否与 设置的 Issuer 值相等。

5.Issuer 验证逻辑可以自定义

6.Issuer 的验证可以关闭

以上设置如无特殊需求直接使用默认值即可,不需要额外设置。

关于以上结论的在代码(API资源)中的实现:

四.如何设置 Token 的 Issuer

第三节讲的是 Issuer 验证时有效 Issuer 的设置,本节讲的是 设置 Token 的 Issuer,Token携带的 Issuer 与API资源设置的有效 Issuer 进行验证匹配完成整个流程,这里提一下,避免搞混。

设置 Token 的 Issuer 需要在 IdentityServer4 设置。在 Startup 里中设置:

services.AddIdentityServer(option=>option.IssuerUri="https://www.baidu.com")

此值必须是一个 http(s) url。

验证是否生效:

1.访问 Discovery Endpoint(/.well-known/openid-configuration)

2.对Token解码,查看 iss 字段

如果在 IdentityServer4 设置此值,默认情况下所有API资源都会获取此值作为默认有效Issuer。

如果你自定义了 Issuer,在使用 Client 访问时会出现 Issuer 与 Authority 不匹配的错误,是因为Client在默认情况下作了限制,关闭即可:

var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest(){Address = "http://localhost:5000" ,Policy = new DiscoveryPolicy(){ValidateIssuerName = false}});

五.默认值问题

如果没有手动设置 IdentityServer4 IssuerUri 值那么它默认会取你访问 IdentityServer4 时的 Host,下面举例说明。

首先修改 IdentityServer4 项目的监听地址,使其能够通过局域网IP访问

然后分别通过 localhost 和 局域网ip 访问 Discovery Endpoint,观察 Issuer 的值:

localhost:

局域网IP:

看出差异了吧,这一点需要注意,下一节将会讲一下这个引发的问题。

六.Issuer 默认值问题可能出现的场景及解决

这种情况一般出现在 IdentityServer4 经过了一层或多层代理,比如 Nginx反代、网关等,外网地址经过代理传递到了 IdentityServer4,如果直接通过外网请求的 Token Endpoint(/connect/token) 生成的 Token,那么这个 Token 携带的 iss 地址将会是外网地址(正常情况下,Host是会经过代理传过来的,如果你不配置传过来,那么就没有这个问题,那么你的后端服务获取的地址与预期肯定有差别,不推荐这种做法)。但是本地API资源(与IdentityServer4在同一台服务器或者同一个局域网)与IdentityServer4交互的地址(Authority)肯定会配成localhost 或者是局域网地址(如果你这里配置成外网地址,那么你可以不继续往下看了,内部交互还要走外部网络严重不推荐甚至是禁止此种做法)。

上图的架构即便是把 Gateway、IdentityServer、Basket服务(API资源)放在一台机器上也是一样的道理,都会出现这种情况,其原因就是如果 IdentityServer 不设置 Issuer,就会取你访问IdentityServer时的Host作为Issuer,外网进来的Host地址和你内部交互的不一样就造成了这个问题,解决办法就是在 IdentityServer 手动指定一个 Issuer 即可解决(第四节),取消掉它的默认取Host的机制,不管你怎么访问IdentityServer返回的Issuer都是一个地址。

七.结束

Demo:

https://github.com/stulzq/IdentityServer4.Samples/tree/master/Practice/03_Issuer

参考资料:

OIDC(OpenId Connect)身份认证授权(核心部分) by blackheart.

最后如果你觉得有用请点击右下角的“推荐”支持一下,十分感谢,写这篇博客花了不少功夫。

IdentityServer4实战 - JWT Issuer 详解的更多相关文章

  1. Asp.Net Core 中IdentityServer4 实战之 Claim详解

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

  2. IdentityServer4实战 - JWT Token Issuer 详解

    原文:IdentityServer4实战 - JWT Token Issuer 详解 一.前言 本文为系列补坑之作,拖了许久决定先把坑填完. 下文演示所用代码采用的 IdentityServer4 版 ...

  3. SVN与TortoiseSVN实战:补丁详解

    硬广:<SVN与TortoiseSVN实战>系列已经写了五篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...

  4. SVN与TortoiseSVN实战:补丁详解(转)

    硬广:<SVN与TortoiseSVN实战>系列已经写了五篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...

  5. 前后端分离,简单JWT登录详解

    前后端分离,简单JWT登录详解 目录 前后端分离,简单JWT登录详解 JWT登录流程 1. 用户认证处理 2. 前端登录 3. 前端请求处理 4. 后端请求处理 5. 前端页面跳转处理 6. 退出登录 ...

  6. BI之SSAS完整实战教程5 -- 详解多维数据集结构

    之前简单介绍过多维数据集(Cube)的结构. 原来计划将Cube结构这部分内容打散,在实验中穿插讲解, 考虑到结构之间不同的部分都有联系,如果打散了将反而不好理解,还是直接一次性全部讲完. 本篇我们将 ...

  7. Windows-007-进程相关命令(netstat、tasklist、taskkill、tskill)实战实例图文详解

    本节主要讲述 Windows 系统下,nestat.tasklist.tskill 三个 CMD 命令的参数,及使用方法:以及如何利用三者结合查看进程信息和结束进程.敬请亲们参阅,希望能对亲们有所帮助 ...

  8. SVN与TortoiseSVN实战:冲突详解(二)

    硬广:<SVN与TortoiseSVN实战>系列已经写了四篇,第二篇<SVN与TortoiseSVN实战:标签与分支>和第三篇<SVN与TortoiseSVN实战:Tor ...

  9. SVN与TortoiseSVN实战:冲突详解(一)

    硬广:<SVN与TortoiseSVN实战>系列已经写了三篇,第一篇<SVN与TortoiseSVN实战:从入门到精通>,第二篇<SVN与TortoiseSVN实战:标签 ...

随机推荐

  1. js设置回车键触发事件

    设置按回车键时触发查询事件: document.onkeydown = function(e){ var ev = document.all ? window.event : e; if(ev.key ...

  2. #Java学习之路——基础阶段二(第一篇)

    我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...

  3. Hadoop系列002-从Hadoop框架讨论大数据生态

    本人微信公众号,欢迎扫码关注! 从Hadoop框架讨论大数据生态 1.Hadoop是什么 1)Hadoop是一个由Apache基金会所开发的分布式系统基础架构 2)主要解决,海量数据的存储和海量数据的 ...

  4. 让你用 Chrome 上网快到想哭:Vimium

    Chrome 已经成了很多人的主力浏览器,你可能被它的简洁快速所吸引,但它日益丰富的「扩展」,你可能从来都没有接触过,而事实上,很多扩展可以挖掘出 Chrome 的潜能,让它变得前所未有的强大.有哪些 ...

  5. 由浅入深讲述MVVM

    相信首次听说MVVM的人,内心都是充满疑惑的!这是个嘛???能干嘛??? MVVM是Model-View-ViewModel的简写.它本质上就是MVC (Model-View- Controller) ...

  6. pandas的apply操作

    pandas的apply操作类似于Scala的udf一样方便,假设存在如下dataframe: id_part pred pred_class v_id 0 d [0.722817, 0.650064 ...

  7. javascript ES6 新特性之 扩展运算符 三个点 ...

    对于 ES6 新特性中的 ... 可以简单的理解为下面一句话就可以了: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. 作用类似于 Object.assign() ...

  8. Spring Cloud实战的代码和视频位置

    大家好,本博文的连接里包含了Spring Cloud实战的代码和视频位置. 代码下载连接: 视频下载连接:

  9. 【重学计算机】计组D3章:运算方法与运算器

    1. 定点数运算及溢出 定点数加减法:减法化加法,用补码直接相加,忽略进位 溢出:运算结果超出了某种数据类型的表示范围 溢出检测方法:统一思想概括为正正得负或负负得正则溢出,正负或负正不可能溢出 方法 ...

  10. java~集合的介绍和使用

    大叔转载,原文地址:http://www.700net.com/rewrite.php/read-4491.html 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了 ...