配置授权服务器端点
Posted
技术标签:
【中文标题】配置授权服务器端点【英文标题】:Configure the authorization server endpoint 【发布时间】:2015-08-26 09:28:04 【问题描述】:问题
我们如何通过用户名和密码流在 ASP.NET 5 中使用不记名令牌?对于我们的场景,我们希望让用户使用 AJAX 调用注册和登录,而无需使用外部登录。
为此,我们需要一个授权服务器端点。 在以前的 ASP.NET 版本中,我们将执行以下操作,然后在 ourdomain.com/Token
URL 登录。
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14)
;
但是,在当前版本的 ASP.NET 中,上述方法不起作用。我们一直在尝试找出新方法。例如,GitHub 上的 aspnet/identity example 配置 Facebook、Google 和 Twitter 身份验证,但似乎没有配置非外部 OAuth 授权服务器端点,除非 AddDefaultTokenProviders()
这样做,在这种情况下,我们想知道 URL 是什么提供者将是。
研究
我们从reading the source here 了解到,我们可以通过在Startup
类中调用IAppBuilder.UseOAuthBearerAuthentication
将“承载身份验证中间件”添加到HTTP 管道。这是一个好的开始,尽管我们仍然不确定如何设置其令牌端点。这不起作用:
public void Configure(IApplicationBuilder app)
app.UseOAuthBearerAuthentication(options =>
options.MetadataAddress = "meta";
);
// if this isn't here, we just get a 404
app.Run(async context =>
await context.Response.WriteAsync("Hello World.");
);
在前往ourdomain.com/meta
时,我们会收到我们的hello world 页面。
进一步的研究表明,我们还可以使用IAppBuilder.UseOAuthAuthentication
扩展方法,并且它需要一个OAuthAuthenticationOptions
参数。该参数具有TokenEndpoint
属性。因此,尽管我们不确定我们在做什么,但我们尝试了这个,这当然没有奏效。
public void Configure(IApplicationBuilder app)
app.UseOAuthAuthentication("What is this?", options =>
options.TokenEndpoint = "/token";
options.AuthorizationEndpoint = "/oauth";
options.ClientId = "What is this?";
options.ClientSecret = "What is this?";
options.SignInScheme = "What is this?";
options.AutomaticAuthentication = true;
);
// if this isn't here, we just get a 404
app.Run(async context =>
await context.Response.WriteAsync("Hello World.");
);
换句话说,去ourdomain.com/token
,没有错误,只是又是我们的hello world页面。
【问题讨论】:
在.Net Core 2.0中需要同样的功能,可以实现吗? 【参考方案1】:编辑 (01/28/2021):作为 3.0 更新的一部分,AspNet.Security.OpenIdConnect.Server 已合并到 OpenIddict。要开始使用 OpenIddict,请访问 documentation.openiddict.com。
好的,让我们回顾一下 OWIN/Katana 3 提供的不同 OAuth2 中间件(以及它们各自的 IAppBuilder
扩展)以及将被移植到 ASP.NET Core 的中间件:
app.UseOAuthBearerAuthentication
/OAuthBearerAuthenticationMiddleware
:它的名字不是很明显,但它负责验证由 OAuth2 服务器中间件颁发的访问令牌(现在仍然是,因为它已被移植到 ASP.NET Core)。它基本上是 cookie 中间件的令牌对应,用于保护您的 API。 在 ASP.NET Core 中,它增加了可选的 OpenID Connect 功能(它现在能够从颁发令牌的 OpenID Connect 服务器自动检索签名证书)。
注意:从 ASP.NET Core beta8 开始,现在命名为app.UseJwtBearerAuthentication
/JwtBearerAuthenticationMiddleware
。
app.UseOAuthAuthorizationServer
/OAuthAuthorizationServerMiddleware
:顾名思义,OAuthAuthorizationServerMiddleware
是一个 OAuth2 授权服务器中间件,用于创建和发布访问令牌。 此中间件不会移植到 ASP.NET Core:OAuth Authorization Service in ASP.NET Core。
app.UseOAuthBearerTokens
:这个扩展并不真正对应于中间件,它只是app.UseOAuthAuthorizationServer
和app.UseOAuthBearerAuthentication
的包装。它是 ASP.NET Identity 包的一部分,只是配置 OAuth2 授权服务器和用于在单个调用中验证访问令牌的 OAuth2 承载中间件的便捷方式。 不会移植到 ASP.NET Core。
ASP.NET Core 将提供一个全新的中间件(我很自豪地说我设计了它):
app.UseOAuthAuthentication
/OAuthAuthenticationMiddleware
:这个新的中间件是一个通用的 OAuth2 交互式客户端,其行为与 app.UseFacebookAuthentication
或 app.UseGoogleAuthentication
完全相同,但它几乎支持任何标准 OAuth2 提供程序,包括您的。 Google、Facebook 和 Microsoft 提供商都已更新为继承这一新的基础中间件。
所以,您真正要寻找的中间件是 OAuth2 授权服务器中间件,又名OAuthAuthorizationServerMiddleware
。
虽然它被大部分社区视为必不可少的组件,但不会移植到 ASP.NET Core。
幸运的是,已经有一个直接替代品:AspNet.Security.OpenIdConnect.Server (https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server)
此中间件是 Katana 3 附带的 OAuth2 授权服务器中间件的高级分支,但它的目标是 OpenID Connect(它本身基于 OAuth2)。它使用相同的低级方法,提供细粒度控制(通过各种通知),并允许您使用自己的框架(Nancy,ASP.NET Core MVC)为您的授权页面提供服务,就像您可以使用 OAuth2 服务器中间件一样.配置很简单:
ASP.NET Core 1.x:
// Add a new middleware validating access tokens issued by the server.
app.UseOAuthValidation();
// Add a new middleware issuing tokens.
app.UseOpenIdConnectServer(options =>
options.TokenEndpointPath = "/connect/token";
// Create your own `OpenIdConnectServerProvider` and override
// ValidateTokenRequest/HandleTokenRequest to support the resource
// owner password flow exactly like you did with the OAuth2 middleware.
options.Provider = new AuthorizationProvider();
);
ASP.NET Core 2.x:
// Add a new middleware validating access tokens issued by the server.
services.AddAuthentication()
.AddOAuthValidation()
// Add a new middleware issuing tokens.
.AddOpenIdConnectServer(options =>
options.TokenEndpointPath = "/connect/token";
// Create your own `OpenIdConnectServerProvider` and override
// ValidateTokenRequest/HandleTokenRequest to support the resource
// owner password flow exactly like you did with the OAuth2 middleware.
options.Provider = new AuthorizationProvider();
);
有一个 OWIN/Katana 3 版本和一个支持 .NET Desktop 和 .NET Core 的 ASP.NET Core 版本。
不要犹豫,试试the Postman sample 以了解它是如何工作的。 我建议阅读 the associated blog post,它解释了如何实现资源所有者密码流程。
如果您仍需要帮助,请随时联系我。 祝你好运!
【讨论】:
通过阅读您所写的内容并将其与this terrific post by Sakimura 相关联,看来 ASP.NET 5 让我们既是依赖方又是身份提供者,或者依赖于外部的第三方身份提供者。对吗? OTB,是的,除了您不能再向您的应用程序本地添加身份提供者(OAuth2 授权服务器尚未移植)。换句话说,您可以成为“依赖方”(或确切地说是“客户端应用程序”)并使用 JWT 令牌(您的 API)保护“资源服务器”,但您不能成为“身份提供者”。我提到的项目允许您将最后一个角色添加到您的应用程序中,这样您就可以成为自己的身份提供者。 OAuth 2.0 规范使用术语“身份验证服务器”。该术语是否与 OpenID Connect 文档使用的身份提供者术语同义? 实际上,OAuth2 规范仅使用术语“授权服务器”而不是“身份验证服务器”(纯粹主义者会告诉您这是因为 OAuth2 是一种授权协议)。当然,在 OpenID Connect 中,身份提供者是授权/身份验证服务器,所以是的,这些概念绝对是同义词。 是的,但我建议直接使用OpenIdConnectServerProvider.GrantResourceOwnerCredentials
通知:使用 ASP.NET Identity 从用户名/密码对创建主体并调用 notification.Validated(principal);
。如果密码的用户名不正确,您可以致电notification.Rejected()
拒绝令牌请求。【参考方案2】:
在@Pinpoint 的帮助下,我们将答案的基本原理拼凑在一起。它显示了组件如何连接在一起,而不是一个完整的解决方案。
提琴手演示
通过我们初步的项目设置,我们能够在 Fiddler 中发出以下请求和响应。
请求
POST http://localhost:50000/connect/token HTTP/1.1
User-Agent: Fiddler
Host: localhost:50000
Content-Length: 61
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=my_username&password=my_password
响应
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 1687
Content-Type: application/json;charset=UTF-8
Expires: -1
X-Powered-By: ASP.NET
Date: Tue, 16 Jun 2015 01:24:42 GMT
"access_token" : "eyJ0eXAiOi ... 5UVACg",
"expires_in" : 3600,
"token_type" : "bearer"
响应提供了一个不记名令牌,我们可以使用它来访问应用程序的安全部分。
项目结构
这是我们在 Visual Studio 中的项目结构。我们必须将其Properties
> Debug
> Port
设置为50000
,以便它充当我们配置的身份服务器。以下是相关文件:
ResourceOwnerPasswordFlow
Providers
AuthorizationProvider.cs
project.json
Startup.cs
Startup.cs
为了便于阅读,我将Startup
类分成两个部分。
Startup.ConfigureServices
对于最基本的,我们只需要AddAuthentication()
。
public partial class Startup
public void ConfigureServices(IServiceCollection services)
services.AddAuthentication();
启动.配置
public partial class Startup
public void Configure(IApplicationBuilder app)
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
// Add a new middleware validating access tokens issued by the server.
app.UseJwtBearerAuthentication(new JwtBearerOptions
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "resource_server_1",
Authority = "http://localhost:50000/",
RequireHttpsMetadata = false
);
// Add a new middleware issuing tokens.
app.UseOpenIdConnectServer(options =>
// Disable the HTTPS requirement.
options.AllowInsecureHttp = true;
// Enable the token endpoint.
options.TokenEndpointPath = "/connect/token";
options.Provider = new AuthorizationProvider();
// Force the OpenID Connect server middleware to use JWT
// instead of the default opaque/encrypted format.
options.AccessTokenHandler = new JwtSecurityTokenHandler
InboundClaimTypeMap = new Dictionary<string, string>(),
OutboundClaimTypeMap = new Dictionary<string, string>()
;
// Register an ephemeral signing key, used to protect the JWT tokens.
// On production, you'd likely prefer using a signing certificate.
options.SigningCredentials.AddEphemeralKey();
);
app.UseMvc();
app.Run(async context =>
await context.Response.WriteAsync("Hello World!");
);
AuthorizationProvider.cs
public sealed class AuthorizationProvider : OpenIdConnectServerProvider
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
// Reject the token requests that don't use
// grant_type=password or grant_type=refresh_token.
if (!context.Request.IsPasswordGrantType() &&
!context.Request.IsRefreshTokenGrantType())
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only grant_type=password and refresh_token " +
"requests are accepted by this server.");
return Task.FromResult(0);
// Since there's only one application and since it's a public client
// (i.e a client that cannot keep its credentials private), call Skip()
// to inform the server that the request should be accepted without
// enforcing client authentication.
context.Skip();
return Task.FromResult(0);
public override Task HandleTokenRequest(HandleTokenRequestContext context)
// Only handle grant_type=password token requests and let the
// OpenID Connect server middleware handle the other grant types.
if (context.Request.IsPasswordGrantType())
// Validate the credentials here (e.g using ASP.NET Core Identity).
// You can call Reject() with an error code/description to reject
// the request and return a message to the caller.
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique identifier]");
// By default, claims are not serialized in the access and identity tokens.
// Use the overload taking a "destinations" parameter to make sure
// your claims are correctly serialized in the appropriate tokens.
identity.AddClaim("urn:customclaim", "value",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Call SetResources with the list of resource servers
// the access token should be issued for.
ticket.SetResources("resource_server_1");
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes("profile", "offline_access");
context.Validate(ticket);
return Task.FromResult(0);
project.json
"dependencies":
"AspNet.Security.OpenIdConnect.Server": "1.0.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
// other code omitted
【讨论】:
看起来不错。不过要说一句:引用 MVC 6 及其服务应该是不必要的。您是否尝试过从ConfigureServices
拨打services.AddAuthentication();
?它在内部调用services.AddWebEncoders()
,它注册了IUrlEncoder
。此外,await Task.Delay(1);
应替换为 return Task.FromResult<object>(null);
以避免不必要的线程切换。
如果使用最后一个包,还需要在ConfigureServices
:services.AddCaching()
中添加缓存服务(目前是Microsoft.Framework.Caching.Memory
包的一部分)
对于那些没有看到接收目的地的 AddClaim 过载的人(无论出于何种原因,我都没有看到),您可以在每个声明上调用 .WithDestination()。
@ShaunLuttin in "Without our rudimentary [...]" 你真的是说 With 吗?
顺便说一句,对于目前的要求,更合适的提供商名称可能是AuthenticationProvider
以上是关于配置授权服务器端点的主要内容,如果未能解决你的问题,请参考以下文章
具有多个grant_type的Spring Oauth2授权服务器用户信息端点不起作用
无法使用“/connect/authorize”端点从 IdentityServer4 获取授权码