ASP.NET没有魔法——ASP.NET OAuth、jwt、OpenID Connect
上一篇文章介绍了OAuth2.0以及如何使用.Net来实现基于OAuth的身份验证,本文是对上一篇文章的补充,主要是介绍OAuth与Jwt以及OpenID Connect之间的关系与区别。
本文主要内容有:
● Jwt简介
● .Net的Jwt实现
● OAuth与Jwt
● .Net中使用Jwt Bearer Token实现OAuth身份验证
● OAuth与OpenID Connect
注:本章内容源码下载:https://files.cnblogs.com/files/selimsong/OAuth2Demo_jwt.zip
Jwt简介
Jwt(Json Web Token)它是一种基于Json用于安全的信息传输标准,Jwt具有以下几个特点:
● 紧凑:Jwt由于是为Web准备的,所以就需要让数据尽可能小,能够在Url、Post参数或者Http Header中携带Jwt,同时由于数据小,所以也增加了数据传输的速度。
● 自包含:在Jwt的playload部分包含了所有应该包含的信息,特别是在Jwt用于身份验证时playload中包含了用户必要的身份信息(注:不应该包含敏感信息),这样在进行身份验证时就无需去数据库中查询用户信息。
● 可信:Jwt是带有数字签名的,可以知道Jwt在传输过程中是否被篡改,保证数据是完整的,可用的签名算法有RS256(RSA+SHA-256)、HS256(HMAC+SHA-256)等。
Jwt有两个用途,其一是用于数据交互,因为Jwt是被签名的,可以保证数据的完整性。另外就是用来携带用户信息进行身份验证。
Jwt包含三个部分:
● Header:包含了签名算法以及令牌类型(默认为JWT)。如:
注:alg以及typ均是缩写,其目的就是为了减小jwt的大小。
● Playload:包含Jwt所携带的信息内容,Playload中包含了3种类型的Claim(声明)定义,分别是标准的,如iss(issuer,Jwt的发行者)、sub(subject,Jwt所代表的用户)、aud(audience,Jwt的接收者)、exp(expiration time,Jwt的过期时间),还有一些是公共约定的如: http://www.iana.org/assignments/jwt/jwt.xhtml,另外就是私有自定义的,这些用来存放具体的信息。
Playload的结构如下:
● Signature:包含了Header以及Playload的base64Url编码后的签名结果,其计算过程如下:
最终三个部分均使用Base64Url的方式进行编码后使用符号“.”进行分隔,以下是一个完整Jwt的例子:
注:Jwt中的数据是透明的,既任何人拿到数据都能Base64Url反编码的形式看到内容,签名仅仅是保证内容不被纂改,所以不能在Jwt中包含敏感数据。以上例子均来自https://jwt.io/introduction/
.Net的Jwt实现
Jwt是一个标准,在https://jwt.io/上可以看到很多不同语言对Jwt的实现,而.Net的其中一个实现是System.IdentityModel.Tokens.Jwt组件,该组件是由微软实现的,它有两个重要的类型分别是:
注:从名称(IdentityModel)都可以看出,微软的这个实现主要是用于身份验证的,如果使用Jwt的目的不是身份验证可以选择其它的组件或自定义实现。
● JwtSecurityToken:这个类型是Jwt的一个封装,它除了包含Jwt的三个要素(Header、Playload、Signature)外,还拓展了一些如Subject、Iusser、Audiences、有效期、签名算法、签名密钥等重要属性。
下图是JwtSecurityToken的部分定义:
● JwtSecurityTokenHandler:该对象用来对Jwt进行操作,如Jwt的创建、验证( 包含发布者、接收者、签名等验证)、Jwt的序列化与反序列化(字符串形式与对象形式之间的转换)
下图是JwtSecurityTokenHandler的部分定义:
OAuth与Jwt
OAuth与Jwt前者是一个授权协议后者是一个信息安全传输标准,看起来它们之间并没有什么关系,但其实OAuth的Access Token有一种实现方式就是Jwt。
为什么要使用Jwt来作为OAuth的Access Token?首先来看一下上一篇文章中生成的Access Token:
它是一个加密后的字符串,该字符串包含了用户的相关信息,但是该字符串只能够被使用Microsoft.Owin.Security.OAuth组件的应用程序解密(不包括参照源码的实现),并且还要保证加解密的密钥是相同的。但是OAuth很多时候是用于一些分布式的场景中,甚至还会使用不同语言来编写不同的应用、服务。这样的话上面这种Token的实现方式就无法满足需求。
所以需要使用Jwt Bearer Token来解决不同应用中的Token识别问题。
.Net中使用Jwt Bearer Token实现OAuth身份验证
在上一篇文章中提到了Microsoft.Owin.Security.OAuth组件中Access Token的生成实际上是对一个AuthenticationTicket对象序列化并加密后的字符串,而Access Token的验证则是对加密后的字符串解密并反序列化获得AuthenticationTicket对象的过程。
而对于Access Token来说无论是Microsoft.Owin.Security.OAuth组件的实现方式还是Jwt,甚至是自定义格式,它的核心都在于如何将用户信息包含到一个字符串令牌中,并且能够通过这个字符串令牌还原出正确的用户信息。对于这一个过程在.Net的Owin身份验证解决方案中将其抽象为一个ISecureDataFormat<TData>接口,其中身份验证的泛型TData类型为AuthenticationTicket。下图是ISecureDataFormat接口的定义,它的两个方法就是用于进行字符串加密令牌与用户信息对象之间的转换,可参考《ASP.NET没有魔法——ASP.NET Identity的加密与解密》
上一篇文章中也给出了Microsoft.Owin.Security.OAuth组件中,默认对Access Token加解密对象是TicketDataFormat,该对象实际上就是一个实现了ISecureDataFormat接口的类型,用于通过数据保护器来完成数据对象的序列化与加解密的工作,可参考《ASP.NET没有魔法——ASP.NET Identity的加密与解密》:
可以这样理解要在.Net中实现基于Jwt Bearer Token的OAuth身份验证,仅需要在Microsoft.Owin.Security.OAuth组件的基础上自定义一个ISecureDataFormat<AuthenticationTicket>类型即可。
Jwt主要属性的说明
实现之前再次对Jwt的一些重要属性进行说明:
● Issuer:发布者,Jwt里面包含并且会进行验证的信息,Token的发布者,该发布者实际上就是身份验证服务器本身。
● Audience:观众,发布者生成一个Token是根据观众来生成的,因为整个验证体系是以发布者为中心的分布式的包含多种应用的,为了保证数据安全一个Token只应该针对其中一个应用有效,所以在验证Jwt时还要对Audience进行验证。
● Subject:主题,在身份验证中一般用于保存用户信息,如用户名。
它们三的关系如下图:
User代表的就是Subject,在OAuth中有Client的概念,OAuth的Client就相当于Audience。之前已经实现了Client的管理,现在为每一个Client添加一个用来数字签名的密钥,该密钥是一个32位byte数组的Base64编码字符串。另外这里是使用HMAC算法来完成对Token的摘要计算。
实现一个基于Jwt的ISecureDataFormat<AuthenticationTicket>
下面就开始介绍如何来实现这个ISecureDataFormat:
1. 通过Nuget安装Microsoft.Owin.Security.Jwt组件:
注:微软实现了一个用于解析Jwt Bearer Token的组件,但是该组件只实现了Unprotect方法,使用这个组件开发可以减少一些工作量。
2. 了解Microsoft.Owin.Security.Jwt中JwtFormat类型:
Microsoft.Owin.Security.Jwt中实现了一个JwtFormat的对象,该对象正好实现了需要的ISecureDataFormat接口:
但是从源码中得知该对象没有实现Protect方法:
而它的UnProtect方法的实现主要工作如下:
● 对发布者以及Token的签名、过期时间等进行验证(注:验证操作是由System.IdentityModel.Tokens.Jwt组件中的JwtSecurityTokenHandler类型提供的)。
● 验证成功后获取Token中包含的用户信息。
3. 实现Jwt的Protect方法:
完整代码:
public class MyJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
//用于从AuthenticationTicket中获取Audience信息
private const string AudiencePropertyKey = "aud"; private readonly string _issuer = string.Empty;
//Jwt的发布者和用于数字签名的密钥
public MyJwtFormat(string issuer)
{
_issuer = issuer;
} public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
//获取Audience名称及其信息
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ?
data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
var audience = ClientRepository.Clients.Where(c => c.Id == audienceId).FirstOrDefault();
if (audience == null) throw new InvalidOperationException("Audience invalid.");
//根据密钥创建用于数字签名的SigningCredentials,该对象在JwtSecurityToken中使用
var keyByteArray = TextEncodings.Base64Url.Decode(audience.Secret);
var signingKey = new InMemorySymmetricSecurityKey(keyByteArray);
var signingCredentials = new SigningCredentials(signingKey,
SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest);
//获取发布时间和过期时间
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
//创建JwtToken对象
var token = new JwtSecurityToken(_issuer,
audienceId,
data.Identity.Claims,
issued.Value.UtcDateTime,
expires.Value.UtcDateTime,
signingCredentials);
//使用JwtSecurityTokenHandler将Token对象序列化成字符串
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
} public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
上面代码做了以下几件事:
● 从AuthenticationTicket中获取Audience信息(注:AuthenticationTicket是.Net中用来保存用户信息的对象,它除了用户信息,如用户名以及用户Claims之外还携带了身份验证的有效期等附加信息,见下图。AuthenticationTicket的创建方式有两种,其一是登录时,在判断登录信息无误后,从数据库中获取相应的用户信息以及从配置(或者默认)获取身份验证信息,如有效期等。另外就是通过反序列化身份Token获取。这里的Protect方法实际上就是序列化Token的方法,所以它得到的AuthenticationTicket是通过第一总方式创建的)
● 创建用于数字签名的SignatureCredentials对象,该对象代表了用于数字签名的算法及其密钥,创建该对象的原因仅仅是JwtSecurityToken对象需要它来完成Token创建。
● 通过JwtSecurityToken对象创建Token,该对象的创建需要发布者(issuer)、观众(audience)、用户Claims信息、发布时间、有效期以及数字签名需要的算法及密钥等。
● 通过JwtSecurityTokenHandler完成对Token的序列化。
3. 在AuthenticationTicket中加入Audience信息。
上面在创建Token时提到了需要Audience信息,而Token是通过AuthenticationTicket创建的,所以需要在创建AuthenticationTicket时加入Audience信息,另外上面也提到AuthenticationTicket的两种创建方法,这里使用的方法就是在“登录”时创建的,而OAuth的“登录”是通过不同类型的“授权”方式实现的,所以要加入Audience信息,只需要在相应方式的授权代码中添加即可(以基于用户名、密码的模式为例,其它方法复制代码即可):
4. 为Audience(Client)添加用于解析Token的JwtBearerAuthentication中间件:
Audience或者说Client包含了受限制的资源,当要访问这些资源时就需要解析Token完成身份验证。而Audience之间或者是Client之间是相对独立的,所以它应该限制可访问的Audience以及拥有自己的加密密钥,甚至还需要验证发布者以确定token的安全性。(注:本例将身份验证服务器和Client都包含在同一个应用中,实际应用可将其分开,这样就是一个简单的单点登录系统)。
5. 运行程序
使用该Token能够正常访问受限资源:
下面是将Token Base64解码后的结果,可以看到Jwt包含的信息:
如果使用test2这个Client获取的Token,将无法访问test1保护的资源:
身份验证失败,跳转登录页面:
OAuth与OpenID Connect
OAuth与OpenID Connect是经常一起出现的两个名词,前者在本系列文章中已经进行过介绍,OAuth是一个授权协议,但是有点矛盾的就是身份验证和授权实际上是两个概念,前面文章也提到过的,身份验证的目的是知道“你”是谁,而授权则是判断“你”是否有权限访问资源。但是从上一篇文章开始介绍的OAuth相关的内容都是用来做身份验证。授权协议用来做身份验证,所以说是矛盾的。
OpenID Connect就是为了弥补OAuth协议的缺陷,而在OAuth协议基础上进行补充拓展的一个身份验证协议。它包含了如发现服务、动态注册、Session管理、注销机制等新的高级特性。
使用OAuth来做身份验证,只是因为OAuth相对简单,适合小型项目,这个与OAuth是授权协议还是身份验证协议无关,它关注的是能否满足需求,包括app.UseOAuthBearerAuthentication方法名称都是Authentication而不是Authorization,通过添加OAuth Bearer身份验证中间件来实现身份验证。OpenID Connect更适合于大型项目,在这里就不再深入介绍。
关于OAuth与OpenID Connect的内容可参考 blackheart的博客。感谢 blackheart给我提的意见。^_^
小结
本章介绍了Jwt以及Jwt在.Net中的实现,并介绍了在.Net中如何使用Jwt Token实现基于OAuth的身份验证。使用Jwt Token最主要的是为了解决不同应用的Token识别问题。
最后简单的说明了OAuth与OpenID Connect的区别,它们取舍的关键点在于需求,对于小型应用来说OAuth就能够满足,而由于OpenID Connect非常复杂,如果有需求时也可以先考虑使用如IdentityServer这些开源组件。
与身份验证相关的内容暂时到此,关于.Net安全相关内容可以参考下面的博客,非常全面包含了身份验证以及.Net中的加解密等内容:https://dotnetcodr.com/security-and-cryptography/
参考:
https://dzone.com/articles/whats-better-oauth-access-tokens-or-json-web-token
https://stackoverflow.com/questions/32964774/oauth-or-jwt-which-one-to-use-and-why
http://openid.net/specs/draft-jones-oauth-jwt-bearer-03.html
https://tools.ietf.org/html/rfc7523
https://auth0.com/learn/json-web-tokens/
https://stackoverflow.com/questions/39239051/rs256-vs-hs256-whats-the-difference
https://stackoverflow.com/questions/18677837/decoding-and-verifying-jwt-token-using-system-identitymodel-tokens-jwt
http://www.c-sharpcorner.com/UploadFile/4b0136/openid-connect-availability-in-owin-security-components/
https://security.stackexchange.com/questions/94995/oauth-2-vs-openid-connect-to-secure-api
https://www.cnblogs.com/linianhui/archive/2017/05/30/openid-connect-core.html
本文链接:http://www.cnblogs.com/selimsong/p/8184904.html
ASP.NET没有魔法——ASP.NET OAuth、jwt、OpenID Connect的更多相关文章
- ASP.NET没有魔法——ASP.NET MVC使用Oauth2.0实现身份验证
随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...
- ASP.NET没有魔法——ASP.NET MVC 与数据库大集合
ASP.NET没有魔法——ASP.NET与数据库 ASP.NET没有魔法——ASP.NET MVC 与数据库之MySQL ASP.NET没有魔法——ASP.NET MVC 与数据库之ORM ASP.N ...
- ASP.NET没有魔法——ASP.NET MVC 路由的匹配与处理
ASP.NET MVC的路由是MVC应用的一个核心也是MVC应用处理的入口,作为一个开发者,在正常情况下仅仅需要做的就是根据需求去定义实体.业务逻辑,然后在MVC的Controller中去调用.Vie ...
- ASP.NET没有魔法——ASP.NET MVC IoC
之前的文章介绍了MVC如何通过ControllerFactory及ControllerActivator创建Controller,而Controller又是如何通过ControllerBase这个模板 ...
- ASP.NET没有魔法——ASP.NET 身份验证与Identity
前面的文章中为My Blog加入了文章的管理功能(ASP.NET没有魔法——ASP.NET MVC使用Area开发一个管理模块),但是管理功能应该只能由“作者”来访问,那么要如何控制用户的访问权限?也 ...
- ASP.NET没有魔法——ASP.NET MVC 过滤器(Filter)
上一篇文章介绍了使用Authorize特性实现了ASP.NET MVC中针对Controller或者Action的授权功能,实际上这个特性是MVC功能的一部分,被称为过滤器(Filter),它是一种面 ...
- ASP.NET没有魔法——ASP.NET Identity 的“多重”身份验证
ASP.NET Identity除了提供基于Cookie的身份验证外,还提供了一些高级功能,如多次输入错误账户信息后会锁定用户禁止登录.集成第三方验证.账户的二次验证等,并且ASP.NET MVC的默 ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(下篇)
上一篇<ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)>文章介绍了ASP.NET MVC模型绑定的相关组件和概念,本章将介绍Controller在执行时是如何通过这 ...
- ASP.NET没有魔法——ASP.NET MVC Razor与View渲染
对于Web应用来说,它的界面是由浏览器根据HTML代码及其引用的相关资源进行渲染后展示给用户的结果,换句话说Web应用的界面呈现工作是由浏览器完成的,Web应用的原理是通过Http协议从服务器上获取到 ...
随机推荐
- PHP-无限级分类
给定省市地区数组如下: $area = array( array('id'=>1,'name'=>'安徽','parent'=>'0'), ...
- zabbix 3.0.4 中文字体替换
zabbix 对中文支持不是很好,会出现乱码: 从windows系统里 找到字体包:如图: 拷贝到zabbix-server里面,注意,把文件名改成小写: 我linux 是centos7.2版本 [r ...
- Netty对WebSocket的支持(五)
Netty对WebSocket的支持(五) 一.WebSocket简介 在Http1.0和Http1.1协议中,我们要实现服务端主动的发送消息到网页或者APP上,是比较困难的,尤其是现在IM(即时通信 ...
- Python爬取视频(其实是一篇福利)
窗外下着小雨,作为单身程序员的我逛着逛着发现一篇好东西,来自知乎 你都用 Python 来做什么?的第一个高亮答案. 到上面去看了看,地址都是明文的,得,赶紧开始吧. 下载流式文件,requests库 ...
- npm安装删除模块以及cnpm淘宝镜像
npm安装模块 [$ npm install xxx]利用 npm 安装xxx模块到当前命令行所在目录: [$ npm install -g xxx]利用npm安装全局模块xxx: npm 删除模块 ...
- ThreadPool.QueueUserWorkItem引发的血案,线程池异步非正确姿势导致程序闪退的问题
ThreadPool是.net System.Threading命名空间下的线程池对象.使用QueueUserWorkItem实现对异步委托的先进先出有序的回调.如果在回调的方法里面发生异常则应用程序 ...
- Error: Can't find Python executable, you can set the PYTHON env variable.
该错误解决方案. NodeJS安装Npm包时出现错误: npm WARN prefer global node-gyp@3.4.0 should be installed with -g > s ...
- while100以内的偶数
#显示100以内的偶数 #声明i i = 1 #开始循环条件为i不等于100,执行while代码块 while i != 100: #给i加1 i +=1 #如果循环到此时i的取余运算为0则打印i i ...
- Linux下SVN提交时强制写日志
Linux版本: 1.在svn的hooks目录下新建一个名为pre-commit的文件并为其添加执行权限(用vi pre-commit直接创建) 2.pre-commit文件的内容如下: #!/bin ...
- SQL Server 插入含有中文字符串出现乱码现象的解决办法
ELECT COLLATIONPROPERTY('Chinese_PRC_Stroke_CI_AI_KS_WS', 'CodePage') --查询SQLServer编码格式的语句 下面 ...