原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide

token认证在最近几年正在成为一个流行的主题,特别是随着移动应用和js应用不断的获得关注。像OAuth 2.0和OpenID Connect这样的基于令牌的标准的广泛采用,已经为令牌引入了更多的开发人员,但是最佳实践并不总是清晰的。

我(作者)在asp.net croe 1.0的时候就开始使用,并且花费了相当长的时间在其中。asp.net core2.0在使用和校验token方面提供了大量的支持,多亏有一个内建的JWT校验中间件。然而,asp.net 4中的关于token生成的代码在asp.net core 2.0中却不见踪影。在asp.net core版本的早期,完整的token认证过程是一件令人困惑的事情。

现在asp.net core 2.1(2.2都发布了)已经稳定,那些令人困惑的事情也得到解决。在这篇文章中,我会检查关于token认证的两个方面的最佳实践:token的校验和token的生成。

什么是token认证

令牌身份验证是将令牌(有时称为访问令牌或承载令牌)附加到HTTP请求以进行身份验证的过程。它通常与服务于移动或SPA (JavaScript)客户端的API一起使用。

到达API的每一个请求都会被检查。如果一个有效的token被发现了,这个请求就是被允许的,如果没有token被发现或者这个token是无效的,那么请求会被一个401未授权响应给拒绝掉。

token认证经常会在OAutho2.0和OpenID Connect这些协议中使用。如果你想看到这些协议是如何工作的,可以查看这里。

在ASP.NET CORE中校验token

在asp.net core的api中添加token认证是一件非常容易的事情,这要多亏了JwtBearerAuthentication这个中间件(在框架中,内建的)。如果你正在消费一个由OpenID Connect服务器创建的token,那么关于配置的过程是非常容易的。

在你的Startup类中的ConfigurationServices方法中的任意地方配置你的中间件,并使用来自授权服务器的值对其进行配置:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "{yourAuthorizationServerAddress}";
options.Audience = "{yourAudience}";
});

然后,在Configure方法中的UseMvc方法的上面,添加一句:

app.UseAuthentication();

第二步中UseAuthentication()是非常容易忘记的。如果经过身份验证的调用不能正常工作,请确保在正确的位置添加了这一行(UseMvc的上面)。

JwtBearer中间件会在到达的请求的请求头中查找token(JSON Web Tokens,JWT)。如果一个有效的token被发现的话,请求就会被授权。然后你将[Authorize]特性添加到你想要保护的controller或者路由上:

[Route("/api/protected")
[Authorize]
public string Protected()
{
return "Only if you have a valid token!";
}

你可能会感觉很奇怪:配置的服务中只有Aurhority和Audience这两个参数被指定,JwtBearer中间件是如何校验传入的token的呢(这个token在request的Authorization header中)。

自动授权服务器元数据

当JwtBearer中间件第一次处理到达的请求,他会试着从授权服务器(也叫做authority或issuer,就是上面代码片段中配置的Authority)中检查一些元数据。这些元数据,或者在OpenID Connect的术语中叫做发现文档(discovery document)的,包含了public key和其他需要校验token的信息。(如果你好奇元数据是什么样子的,这里有一个OpenID Connect的发现文档,就是指的元数据。

如果JwtBearer中间件发现了这个元数据的文档,他(JwtBearer)会自动配置校验(通过配置TokenValidationParameters这个类型的属性),真几把棒!

如果没有发现这个元数据文档,你会得到一个错误:

System.IO.IOException: IDX10804: Unable to retrieve document from: "{yourAuthorizationServerAddress}".
System.Net.Http.HttpRequestException: Response status code does not indicate success: (Not Found).

如果你的授权服务器没有发布这个元数据文档,或者你想要自己配置TokenValidationParameters,你可以在中间件的配置中手动的配置他们。

指定TokenValidationParameters

如果你想要细粒度的控制token的校验过程,可以使用JwtBearer中提供的配置:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// Clock skew compensates for server time drift.
// We recommend 5 minutes or less:
ClockSkew = TimeSpan.FromMinutes(),
// Specify the key used to sign the token:
IssuerSigningKey = signingKey,
RequireSignedTokens = true,
// Ensure the token hasn't expired:
RequireExpirationTime = true,
ValidateLifetime = true,
// Ensure the token audience matches our audience value (default true):
ValidateAudience = true,
ValidAudience = "api://default",
// Ensure the token was issued by a trusted authorization server (default true):
ValidateIssuer = true,
ValidIssuer = "https://{yourOktaDomain}/oauth2/default"
};
});

TokenValidationParamters中最常用的选项是issuer、audience和clock skew。同时你需要提供提供token签名的key(密钥),这些key看起来会有所不同,取决于你使用的是对称加密还是非对称加密。

理解对称签名和非对称签名

授权服务器生成的token会使用对称或非对称的签名。如果你的授权服务器发布了一个元数据文档(discovery document),在文档中会包含关于密钥的信息(是一个public key),所有你通常不需要关心它是如何进行的。

然而,如果你要自己配置中间件,或者要手动的校验token,你需要理解你的token是如何被签名的。这两种(对称和非对称)的签名之间有什么不同的地方?

对称签名/对称key

一个对称key,也叫做shared key或者shared secret,是一个在API和授权服务器中同时保存着的、保密的值(例如密码)。并且授权服务器颁发token。授权服务器使用shared key对token进行签名,同时API使用同样的key对token进行校验。

如果你有一个shared key,很容易在JwaBearer中间件中使用:

// For example only! Don't store your shared keys as strings in code.
// Use environment variables or the .NET Secret Manager instead.
var sharedKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("mysupers3cr3tsharedkey!")); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// Specify the key used to sign the token:
IssuerSigningKey = sharedKey,
RequireSignedTokens = true,
// Other options...
};
});

你要确保key被安全的保存,向上面代码中那样的保存你的key不是一个好主意。应该将它保存到环境变量或者使用.NET Secret Manager. ASP.NET Core configuration model 会很容易的将这个工作完成:

var sharedKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["SigningKey"]);

同样,不要在前端代码中存储共享密钥或将其公开给浏览器。它必须保存在服务器上。

非对称签名/非对称key

使用非对称签名的话,你不需要在你的服务器上面保存一个密钥(private key)。相反,一个public/private key(成对出现的)会被使用:授权服务器使用private key对token进行签名,并且发布一个public key,public key用来校验token。

通常情况下,public key的信息会自动的从元数据文档(discovery document)中检查和获取(就像上面那个链接展示的discovery document)。如果你要手动的指定他,你需要从授权服务器获取关键参数并创建一个SecurityKey对象:

// Manually specify a public (asymmetric) key published as a JWK:
var publicJwk = new JsonWebKey
{
KeyId = "(some key ID)",
Alg = "RS256",
E = "AQAB",
N = "(a long string)",
Kty = "RSA",
Use = "sig"
};

多数场景下,public key可以从授权服务器的JSON Web Key集(JWKS)中获取到。授权服务器可能会定期的旋转(rotate)这个key,所以,你需要定期的检查更新的key(从授权服务器中)。但是如果你让JwtBearer中间件自动的配置(通过discovery document),这一切都是自动进行的!

asp.net core中手动校验token

在一些情况下,你可能想要不通过JwtBearer中间件来校验token。使用这个中间件是首选,因为它可以很好的插入到asp.net core的授权系统中。

如果你真的想要手动的校验JWT,你可以使用System.IdentityModel.Tokens.Jwt 包中的JwtSecurityTokenHandler。它使用同样的TokenValidationParamters类来指定校验选项:

private static JwtSecurityToken ValidateAndDecode(string jwt, IEnumerable<SecurityKey> signingKeys)
{
var validationParameters = new TokenValidationParameters
{
// Clock skew compensates for server time drift.
// We recommend 5 minutes or less:
ClockSkew = TimeSpan.FromMinutes(),
// Specify the key used to sign the token:
IssuerSigningKeys = signingKeys,
RequireSignedTokens = true,
// Ensure the token hasn't expired:
RequireExpirationTime = true,
ValidateLifetime = true,
// Ensure the token audience matches our audience value (default true):
ValidateAudience = true,
ValidAudience = "api://default",
// Ensure the token was issued by a trusted authorization server (default true):
ValidateIssuer = true,
ValidIssuer = "https://{yourOktaDomain}/oauth2/default"
}; try
{
var claimsPrincipal = new JwtSecurityTokenHandler()
.ValidateToken(jwt, validationParameters, out var rawValidatedToken); return (JwtSecurityToken)rawValidatedToken;
// Or, you can return the ClaimsPrincipal
// (which has the JWT properties automatically mapped to .NET claims)
}
catch (SecurityTokenValidationException stvex)
{
// The token failed validation!
// TODO: Log it or display an error.
throw new Exception($"Token failed validation: {stvex.Message}");
}
catch (ArgumentException argex)
{
// The token was not well-formed or was invalid for some other reason.
// TODO: Log it or display an error.
throw new Exception($"Token was invalid: {argex.Message}");
}
}

如果你的授权服务器发布了一个元数据文档,你可以通过 Microsoft.IdentityModel.Protocols.OpenIdConnect 包中的OpenIdConnectConfigurationRetriever类来对这个文档中的内容进行检索,这个过程会自动的获取签名key:

var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
// .well-known/oauth-authorization-server or .well-known/openid-configuration
"{yourAuthorizationServerAddress}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever()); var discoveryDocument = await configurationManager.GetConfigurationAsync();
var signingKeys = discoveryDocument.SigningKeys;

它负责令牌身份验证的验证端,但是如何生成令牌本身呢?

asp.net core中生成用于认证的token

回首asp.net 4.5的那段日子,UseOAuthAuthorizationServer中间件给你带来一个可以轻易生成token的端点。然后,asp.net core团队决定不要引入这个组件( decided not to bring it to ASP.NET Core,),这意味着你需要其他的东西来达成这个功能。也就是说你需要找到一个或者自己构建一个能够生成token的授权服务器。

通常有以下两种方式:

① 使用一个云服务像Azure AD B2C 或者 Okta

② 你自己构建或者配置一个

Hosted Authorization Server with Okta

用一个宿主授权服务器来生成token是最简单的。通过在上面注册一个账号然后根据网站的提示一步一步个进行下去之后,在你自己的应用中配置是非常简单的:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://{yourOktaDomain}/oauth2/default";
options.Audience = "api://default";
});

还有其他的一些:

OpenIddict

OpenIddict is an easy-to-configure authorization server that works nicely with ASP.NET Core Identity and Entity Framework Core. It plugs right into the ASP.NET Core middleware pipeline and is easy to configure.

OpenIddict is a great choice if you’re already using ASP.NET Core Identity and want to generate tokens for your users. You can follow Mike Rousos’ in-depth tutorial on the MSDN blog to set it up and configure it in your application.

IdentityServer4

Thinktecture’s open-source IdentityServer project has been around for a long time, and it got a major update for .NET Core with IdentityServer4. Of the three packages discussed here, it’s the most powerful and flexible.

IdentityServer is a good choice when you want to roll your own full-fledged OpenID Connect authorization server that can handle complex use cases like federation and single sign-on. Depending on your use case, configuring IdentityServer4 can be a little complicated. Fortunately, the official documentation covers many common scenarios.

Token Authentication Can Be Complex!

I hope this article helps it feel a little less confusing. The ASP.NET Core team has done a great job of making it easy to add token authentication to your ASP.NET Core API, and options like OpenIddict and Okta make it easy to spin up an authorization server that generates tokens for your clients.

Here are some more resources if you want to keep learning:

【翻译】asp.net core2.0中的token认证的更多相关文章

  1. 在ASP.NET Core2.0中使用百度在线编辑器UEditor(转)

    一.起因 UEditor是百度旗下的富文本编辑器,对于后端上传处理仅提供了Asp.Net 版的支持. 如果想在.Net Core项目中使用,那么后台上传接口需要重构. UEditorNetCore:百 ...

  2. asp.net core2.0中异常的处理

    最近在开发中遇到一些关于如何抛出异常的困惑,在qq群里进行了讨论,有些人认为抛出异常是有理由的,可以对业务流程进行控制,而有些认为抛出异常会导致程序性能低下,我写一些自己的心得吧. 异常的全局处理 a ...

  3. asp.net core2.0中网站发布的时候,怎么样才配置才可以使视图文件不被打包进去?

    默认设置可真是坑~~ https://q.cnblogs.com/q/99680/

  4. asp.net core2.0中网站发布的时候,视图文件不被打包成dll

    项目csproj文件里面加 <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <Target ...

  5. [翻译]在asp.net core2.0 OpenID Connect Handler中丢失了声明(CLaims)?

    注:这是一篇翻译,来自这里.这篇文章讲述了在asp.net core2.0中使用openid connect handler的过程中解析不到你想要的claim时,你可以参考这篇文章. Missing ...

  6. 一步一步带你做WebApi迁移ASP.NET Core2.0

    随着ASP.NET Core 2.0发布之后,原先运行在Windows IIS中的ASP.NET WebApi站点,就可以跨平台运行在Linux中.我们有必要先说一下ASP.NET Core. ASP ...

  7. VS2017创建一个 ASP.NET Core2.0 应用,并搭建 MVC 框架

    https://testerhome.com/topics/11747 1.使用最新版本的VS2017,并安装.NET Core2.0中相关开发工具   2.打开VS2017,点击文件-新建-项目,选 ...

  8. WebApi迁移ASP.NET Core2.0

    WebApi迁移ASP.NET Core2.0 一步一步带你做WebApi迁移ASP.NET Core2.0   随着ASP.NET Core 2.0发布之后,原先运行在Windows IIS中的AS ...

  9. .Net Core技术研究-WebApi迁移ASP.NET Core2.0

    随着ASP.NET Core 2.0发布之后,原先运行在Windows IIS中的ASP.NET WebApi站点,就可以跨平台运行在Linux中.我们有必要先说一下ASP.NET Core. ASP ...

随机推荐

  1. [开源]Entity Framework 6 Repository 一种实现方式

    在使用Entity Framework这种ORM框架得时候,一般结合Repository仓储形式来处理业务逻辑:虽然这种模式带来很多好处,但是也会引发一些争议,在此抛开不谈,小弟结合项目经验来实现一下 ...

  2. JAVA 探究NIO

    事情的开始 1.4版本开始,java提供了另一套IO系统,称为NIO,(New I/O的意思),NIO支持面向缓冲区的.基于通道的IO操作. 1.7版本的时候,java对NIO系统进行了极大的扩展,增 ...

  3. 102 - kube-scheduler源码分析 - cobra-寻找scheduler组件启动函数

    main函数在哪里? 看到这个go文件时大家是不是有一种找到入口的欣喜,同时有一种难以言表的郁闷,为什么那么短?获取一个command,然后执行一个Execute()就运行了?好像是这么回事,然后点开 ...

  4. 使用Atlas进行元数据管理之Type(类型)

    背景:笔者和团队的小伙伴近期在进行数据治理/元数据管理方向的探索, 在接下来的系列文章中, 会陆续与读者们进行分享在此过程中踩过的坑和收获. 元数据管理系列文章: [0] - 使用Atlas进行元数据 ...

  5. TensorFlow tutorial

    代码示例来自https://github.com/aymericdamien/TensorFlow-Examples tensorflow先定义运算图,在run的时候才会进行真正的运算. run之前需 ...

  6. 学JAVA第十六 天,JAVA多态

    今天老师讲了多态的使用 多态是同一个行为具有多个不同表现形式或形态的能力. 多态的优点: 1. 消除类型之间的耦合关系 2. 可替换性  3. 可扩充性 4. 接口性 5. 灵活性 6. 简化性 我个 ...

  7. JavaScript 是如何工作的:JavaScript 的内存模型

    摘要: 从内存角度理解 let 和 const 的意义. 原文:JavaScript 是如何工作的:JavaScript 的内存模型 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这 ...

  8. nlp中文分词(jieba和pyltp)

    分词是中文自然语言处理的基础.目前常用的分词算法有 1.张华平博士的NShort中文分词算法. 2.基于条件随机场(CRF)的中文分词算法. 这两种算法的代表工具包分别是jieba分词系统和哈工大的L ...

  9. 工具资源系列之给虚拟机装个ubantu

    前文我们已经讲解了如何在 mac 系统上安装虚拟机软件,这节我们接着讲解如何利用虚拟机安装 Ubuntu 镜像. 安装镜像的大致步骤基本相同,只不过是配置项略显不同而已,如果需要安装其他系统镜像,请参 ...

  10. ASP.NET Zero--WEB.HOST应用程序

    WEB.HOST应用程序 AspNet Zero解决方案包含一个额外的项目Web.Host,它将所有应用程序功能公开为API.因此,您可以从任何设备使用API​​.实际上,Web.Mvc项目也是这样做 ...