#asp.net #oauth-2.0 #owin #owin.security
#asp.net #oauth-2.0 #owin #owin.security
Вопрос:
Я пытаюсь настроить свою аутентификацию с помощью 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_codeamp;code=xxxxxxamp;client_id=xxxxxamp;redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded
Он проходит:
OnMatchEndpoint
OnValidateClientAuthentication
И это все. Я ожидал, что он вызовет OnValidateTokenRequest
и OnGrantAuthorizationCode
next, но этого просто не произошло. Я понятия не имею, почему.
xxxx
В запросе не являются заполнителями, я пробовал это так. Может быть, промежуточное программное обеспечение выполняет некоторые проверки самостоятельно и отклоняет запрос из-за этого? Я пробовал варианты redirect_uri
с http
, без какого-либо протокола, без косой черты в конце…
Он также работает должным образом с пользовательским grant_type
. Так что, если я слишком отчаялся, я думаю, я могу использовать это для имитации authorization_code
, но я бы предпочел этого не делать.
TL; DR
Мой OAuthAuthorizationServerProvider
возвращает {"error":"invalid_grant"}
после OnValidateClientAuthentication
при использовании grant_type=authorization_code
.
- Почему это останавливается там?
- Как я могу заставить всю эту чертову штуку работать?
Спасибо за вашу помощь!
Редактировать
Как указал РаджешКаннан, я допустил ошибку в своей конфигурации. Я не предоставил AuthorizationCodeProvider
экземпляр. Однако это не полностью решило проблему, поскольку в моем случае код не выдается AuthorizationCodeProvider
, и я не могу просто десериализовать его. Я воспользовался обходным путем, который у меня сработал.
Ответ №1:
Вот что у меня получилось. Меня не совсем устраивает это решение, но оно работает и должно помочь другим исправить их проблемы.
Итак, проблема в том, что я не установил 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 amp;amp; 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);
}
}
}
}
Комментарии:
1. Привет @scenario, тебе удалось найти лучший способ? У меня такая же проблема
2. Извините, я прекратил работу над этим проектом, поэтому у меня нет дальнейших обновлений по этой теме. Однако это решение сработало хорошо. API просто разработан с учетом других вариантов использования. Возможно, в будущих обновлениях это будет выполнимо без жестко закодированных ключей, приведений и ко.
Ответ №2:
У меня была та же ошибка. Чего мне не хватало:
- Укажите
OAuthAuthorizationServerOptions.AuthorizationCodeProvider
в соответствии с документацией. - Укажите то же самое
client_id
, что и GET-параметр, при отправке запроса к конечной точке токена, что и при полученииauthorization_code
. - Переопределите
OAuthAuthorizationServerProvider.ValidateClientAuthentication
и в этом вызове методаcontext.TryGetFormCredentials
. Это устанавливает свойствоcontext.ClientId
в значение изclient_id
GET-параметра. Это свойство должно быть установлено, иначе вы получитеinvalid_grant
ошибку. Кроме того, вызовcontext.Validated()
.
После выполнения всего вышеперечисленного я смог, наконец, обменять authorization_code
на access_token
в конечной точке токена.
Комментарии:
1. У меня не работает. Я делаю все это, но в то время как мои реализации для
OAuthAuthorizationServerProvider.ValidateClientAuthentication
иIAuthenticationTokenProvider.ReceiveAsync
вызываются (в таком порядке), моя реализация дляOAuthAuthorizationServerProvider.GrantAuthorizationCode
никогда не запускается, и в итоге я получаю 400: invalid_grant.2. плюс 1, хотя, поскольку предложение client_id было хорошим и действительно требовалось.
Ответ №3:
Благодаря сценарию, в моем коде отсутствовали следующие два обязательных значения. Опубликовано здесь на случай, если другие сочтут это полезным:
// 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);
Ответ №4:
Убедитесь, что вы настроили параметры сервера авторизации. Я думаю, вам следует предоставить данные о конечной точке авторизации:
AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
По ссылке ниже будет подробно объяснено предоставление кода авторизации, и в нем перечислены методы, которые были задействованы в жизненном цикле предоставления кода авторизации.
Комментарии:
1. Спасибо, ваш ответ направил меня на правильный путь. Я отредактировал вопрос, чтобы объяснить, что мне пришлось изменить. Но это просто кажется странным. Мне не хватает более простого способа получить параметры запроса и передать их поставщику? Или, может быть, я не должен связываться с моим сервером единого входа таким образом?
Ответ №5:
Ответ @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."
);
Комментарии:
1. Я несколько раз попадался в эту ловушку 😀 Это не имеет ничего общего с этими фреймворками, но вам нужно передать тип аутентификации конструктору, если вы хотите аутентифицированный идентификатор, это можно найти в примечаниях в документах: learn.microsoft.com/en-us/dotnet/api /…
Ответ №6:
Я решил это с помощью следующего простейшего примера и хотел бы поделиться им. Надеюсь, кто-нибудь найдет это полезным.
—
Кажется, что промежуточное программное обеспечение проверит, 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)amp;amp;("expected-clientSecret"==clientSecret)) {
context.Validated(); // <-
return Task.CompletedTask;
}
}
return base.ValidateClientAuthentication(context);
}
public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
}
}
Обратите внимание, что если вы вызываете context.Validated
с определенным идентификатором клиента, то вам нужно будет указать тот же client_id в свойствах заявки, вы можете сделать это с помощью метода AuthenticationTokenProvider.Receive