我正在尝试使用authorization_code
授权流设置我的身份验证。我之前用过grant_type=password
,所以我知道它应该是怎么工作的。但是当使用grant_type=authorization_code
时,我不能使它返回invalid_grant
以外的任何东西
下面是我的设置:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/auth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SampleAuthProvider()
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AuthenticationType = "Bearer"
});
SampleAuthProvider是以下类:https://gist.github.com/anonymous/8a0079b705423b406c00
基本上,它只是记录每一步并验证它。我尝试了请求:
POST http://localhost:12345/auth/token
grant_type=authorization_code&code=xxxxxx&client_id=xxxxx&redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded
它正在通过:
-
OnMatchEndpoint
-
OnValidateClientAuthentication
就这些。我期望它接下来调用OnValidateTokenRequest
和OnGrantAuthorizationCode
,但它没有。我也不知道为什么。
请求中的xxxx
不是占位符,我试过了。也许中间件自己做一些检查,并因此拒绝请求?我尝试了redirect_uri
与http
的变体,没有任何协议,没有尾斜杠…
它也可以与自定义grant_type
一起正常工作。如果我太绝望了,我想我可以用它来模拟authorization_code
,但我宁愿不这样做。
TL;博士
当使用grant_type=authorization_code
时,我的OAuthAuthorizationServerProvider
在OnValidateClientAuthentication
之后返回{"error":"invalid_grant"}
。
- 为什么停在那里?
- 我怎么才能让这该死的东西工作?
谢谢你的帮助!
编辑
正如RajeshKannan指出的,我在配置中犯了一个错误。我没有提供AuthorizationCodeProvider
实例。然而,这并没有完全解决问题,因为在我的情况下,代码不是由AuthorizationCodeProvider
发布的,我不能对它进行反序列化。
这是我得到的工作。我对这个解决方案不是很满意,但它有效,应该可以帮助其他人解决他们的问题。
所以,问题是我没有设置AuthorizationCodeProvider
属性。当收到带有grant_type=authorization_code
的请求时,该代码必须由该代码提供程序验证。框架假定代码是由代码提供程序发出的,但我的情况并非如此。我从另一个服务器得到它,并且必须将代码发回验证。
在标准情况下,你也是发布代码的人,RajeshKannan提供的链接描述了你必须做的一切。
这里是你必须设置属性的地方:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(Paths.TokenPath),
Provider = new SampleAuthProvider(),
AuthorizationCodeProvider = new MyAuthorizationCodeProvider ()
}
和MyAuthorizationCodeProvider
类的声明:
internal class MyAuthorizationCodeProvider : AuthenticationTokenProvider
{
public override async Task ReceiveAsync(
AuthenticationTokenReceiveContext context)
{
object form;
// Definitely doesn't feel right
context.OwinContext.Environment.TryGetValue(
"Microsoft.Owin.Form#collection", out form);
var redirectUris = (form as FormCollection).GetValues("redirect_uri");
var clientIds = (form as FormCollection).GetValues("client_id");
if (redirectUris != null && clientIds != null)
{
// Queries the external server to validate the token
string username = await MySsoService.GetUserName(context.Token,
redirectUris[0]);
if (!string.IsNullOrEmpty(username))
{
var identity = new ClaimsIdentity(new List<Claim>()
{
// I need the username in GrantAuthorizationCode
new Claim(ClaimTypes.NameIdentifier, username)
}, DefaultAuthenticationTypes.ExternalBearer);
var authProps = new AuthenticationProperties();
// Required. The request is rejected if it's not provided
authProps.Dictionary.Add("client_id", clientIds[0]);
// Required, must be in the future
authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
var ticket = new AuthenticationTicket(identity, authProps);
context.SetTicket(ticket);
}
}
}
}
我也有同样的错误。我错过的东西:
- 根据文档指定
OAuthAuthorizationServerOptions.AuthorizationCodeProvider
- 在向令牌端点发出请求时,指定与收到
authorization_code
时相同的client_id
作为get参数。 - 覆盖
OAuthAuthorizationServerProvider.ValidateClientAuthentication
,并在此方法中调用context.TryGetFormCredentials
。这将属性context.ClientId
设置为client_id
get参数的值。必须设置此属性,否则将得到invalid_grant
错误。同时,调用context.Validated()
。
在完成上述所有操作之后,我最终可以在令牌端点将authorization_code
交换为access_token
。
感谢场景,我的代码缺少以下两个必需的值。在这里发布,以防其他人觉得有用:
// Required. The request is rejected if it's not provided
authProps.Dictionary.Add("client_id", clientIds[0]);
// Required, must be in the future
authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
确保您已经配置了授权服务器选项。我认为你应该提供你的授权终端详细信息:
AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
下面的链接将详细说明授权码授予,并列出授权码授予生命周期中涉及的方法。
拥有Oauth授权服务器
@dgn的答案或多或少对我有用。这只是它的扩展。事实证明,您可以向ClaimsIdentity
构造函数提供任何想要的字符串。下面的代码同样有效,并且可以用作详细的代码注释:
var identity = new ClaimsIdentity(
@"Katana - What a shitty framework/implementation.
Unintuitive models and pipeline, pretty much have to do everything, and the docs explain nothing.
Like what can go in here? WTF knows but turns out as long as _something_ is in here,
there is a client_id key in your AuthenticationProperties with the same value as
what's set inside your implementation for OAuthAuthorizationServerProvider.ValidateClientAuthentication, and
your AuthenticationProperties.ExpiresUtc is set to some time in the future, it works.
Oh and you don't actually need to supply an implementation for OAuthAuthorizationServerProvider.GrantAuthorizationCode...
but if you are using the resource owner grant type, you _do_ need to supply an implementation of
OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials. Hmm. Whatever.
Katana and IdenetityServer - two frameworks that are absolute garbage. In the amount of time it took me to
figure out all the observations in this paragraph, I could've written my own /token endpoint."
);
我用下面这个最简单的例子解决了这个问题,我想和大家分享一下。希望对大家有所帮助。
,
似乎中间件将检查redirect_uri
键是否存在于AuthenticationProperties
的字典中,删除它,一切工作正常(具有验证的上下文)。
AuthorizationCodeProvider
的简化示例如下:
public class AuthorizationCodeProvider:AuthenticationTokenProvider {
public override void Create(AuthenticationTokenCreateContext context) {
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context) {
context.DeserializeTicket(context.Token);
context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
}
}
不要忘记在重写的方法OAuthAuthorizationServerProvider.ValidateClientAuthentication
中验证上下文。同样,这里有一个从模板项目的ApplicationOAuthProvider
类继承的简化示例:
public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
if(null!=context.RedirectUri) {
context.Validated(context.RedirectUri);
return Task.CompletedTask;
}
return base.ValidateClientRedirectUri(context);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
// Specify the actual expected client id and secret in your case
if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {
context.Validated(); // <-
return Task.CompletedTask;
}
}
return base.ValidateClientAuthentication(context);
}
public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
}
}
请注意,如果使用特定的客户端id调用context.Validated
,则必须将相同的client_id放入票据的属性中,可以使用方法AuthenticationTokenProvider.Receive