OWIN Оператор обновления, вставки или удаления магазина затронул неожиданное количество строк (0) Ошибка - PullRequest
3 голосов
/ 11 апреля 2020

Я столкнулся с очень странной проблемой, когда токен refre sh "исчезает" через несколько часов в службе приложений Azure, в которой размещается мой проект Wep Api. Я реализовал OAuth для своего потока паролей. Срок действия нашего AccessToken истекает через 1 час, а нашего RefreshToken истекает через одну неделю.

Для некоторого фона. Это происходит в службе приложений Azure, где я размещаю свой веб-интерфейс, и мобильный интерфейс выполняет вызовы к нему (есть несколько пользователей / мобильных устройств, которые звонят в эту службу приложения).

Вот как выглядит пример исходного вызова с использованием вызова /token:

enter image description here

Мой grant_type - это пароль. Обычно я получаю обратно поле refresh_token вместе с access_token, token_type и expires_in.

Работает нормально в течение первых нескольких часов после того, как я пу sh в службу приложений, затем refresh_token исчезает , Я действительно озадачен этой проблемой.

Вот мой CreateAsync Код:

public async Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        var clientid = context.Ticket.Properties.Dictionary["as:client_id"];

        if (string.IsNullOrEmpty(clientid))
        {
            return;
        }

        string refreshTokenId = await CreateRefreshTokenId(clientid, context);

        if (refreshTokenId != null)
        {
            context.SetToken(refreshTokenId);
        }
        else
        {
            throw new Exception("refresh token could not be created");
        }
    }

    private async Task<string> CreateRefreshTokenId(string clientId, AuthenticationTokenCreateContext context)
    {
        var ticket = context.Ticket;
        var refreshTokenId = Guid.NewGuid().ToString("n");
        var refreshTokenLifeTime = ConfigurationManager.AppSettings["as:clientRefreshTokenLifeTime"];

        var token = new CreateRefreshTokenDTO
        {
            RefreshTokenId = refreshTokenId,
            ClientId = clientId,
            Subject = ticket.Identity.Name,
            IssuedUtc = DateTime.UtcNow,
            ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
        };

        ticket.Properties.IssuedUtc = token.IssuedUtc;
        ticket.Properties.ExpiresUtc = token.ExpiresUtc;

        token.ProtectedTicket = context.SerializeTicket();

        var result = await createRefreshTokenManager.ManagerRequest(new CreateRefreshTokenRequest
        {
            RefreshToken = token
        });

        return result.IsError ? null : refreshTokenId;
    }

Я добавил исключение в оператор else, чтобы посмотреть, будет ли оно выбрасывать и исключение, и оно на самом деле бросает, что приводит меня к мысли, что refreshTokenId является нулевым. Я также добавил ведение журнала в таблицу журналов, но по какой-то причине, когда выдается эта ошибка, она должна быть сохранена в таблице БД (которую я тестировал локально и работает), но на сервере приложений она не сохраняется в таблице. Очень сбивает с толку ... ОБНОВЛЕНИЕ: ПОЖАЛУЙСТА, СМОТРИТЕ ОБНОВЛЕНИЕ НИЖЕ, ПОЧЕМУ БЕЗ ЛОГОВ НЕ СОХРАНИЛСЯ

Затем, то, что должно произойти после этого, это то, что теперь передний конец (мобильный, в этом case) имеет токены доступа и refre sh; по истечении срока действия токена делается еще один вызов /token, но с grant_type = refresh_token: enter image description here

ОБНОВЛЕНИЕ

В конце концов я смог воспроизвести проблему локально методом проб и ошибок и дождался окончания срока действия маркера доступа (не совсем уверен). Но в любом случае я смог выдать эту ошибку:

Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.

Эта ошибка была причиной, по которой я не смог сохранить какие-либо журналы в БД.

Я использую Castle Windsor как мой Io C и EF6. Мои звонки в следующем порядке:

1] Попытка проверить контекст. Здесь я делаю еще один await вызов LoginUserManager, где я в основном получаю и проверяю информацию пользователя

// This is used for validating the context
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)

2] CreateAsyn c для создания доступа и обновления sh токенов из Context

public async Task CreateAsync(AuthenticationTokenCreateContext context)

Внутри CreateAsyn c Я делаю await вызов CreateOrUpdateRefreshTokenManager, который либо выполняет обновление, если запись существует, либо создание. И в конечном итоге сделать SaveChanges(). Это SaveChanges() и является причиной ошибки Если я не вызову SaveChanges(), в этой таблице не обновляется и не создается запись. Это странно, потому что в других частях моего кода я вообще не вызываю SaveChanges() в конце жизненного цикла веб-запроса, но производится обновление / создание / удаление. Я предполагаю, что EF / Windsor обрабатывает сохранение для меня.

Я думаю, что, поскольку этот поток отличается от всех других моих конечных точек, и что его обработка двух Asyn c вызывает, что где-то посередине я избавляюсь от DbContext и, возможно, именно поэтому я вижу его сбой при втором (CreateAsync) вызове. Не уверен, просто моя мысль здесь.

Во всяком случае, извините за длинную почту здесь. Я хотел опубликовать как можно больше информации и надеюсь, что это также может помочь кому-то, кто сталкивается с этой или подобной проблемой.

Спасибо!

ОБНОВЛЕНИЕ 2

Я заметил, что после получения этой ошибки при вызове /token, любые другие (AllowAnonymous) вызовы, которые я выполняю, даже те, которые связаны с БД. Но, в частности, вызов /token больше не работает. Единственный способ обойти это - перезапустить сервер.

ОБНОВЛЕНИЕ 3 Мне удалось воспроизвести эту проблему ТОЛЬКО на мобильном тестировании (связанном с Azure сервером), но я не могу воспроизвести локально. Шаги, которые я использовал для воспроизведения:

  1. Вход с одной учетной записью
  2. Выход
  3. Вход с другой учетной записью
  4. Выход
  5. Войдите в систему с первой учетной записью, которую я пытался) - This FAILS

1 Ответ

0 голосов
/ 23 апреля 2020

Хорошо, я смог разобраться в этой проблеме, и я приложу все усилия, чтобы описать, что здесь происходит.

Для тех из вас, кто следовал учебному пособию, например this один или любой другой подобный, вы увидите, что у вас в основном настроена некоторая структура хранилища наряду с, может быть, вашим собственным контекстом, который вы наследуете базовый контекст, верно?

В моем случае я обрабатывал контекст Dispose в конце запроса, переопределяя метод Dispose (удаление bool), найденный в классе ApiController. Если вы создаете WebApi, вы поймете, о чем я говорю, потому что все написанные вами контроллеры наследуют это. И если у вас есть какой-то Io C, настроенный с Lifetimes, установленным в конце запроса, он будет располагаться там:)

Однако, в этом случае вызова /token я заметил, что мы никогда не использовали какие-либо контроллеры ... или, по крайней мере, ни один из них, использующий ApiController, поэтому я даже не смог использовать этот метод Dispose. Это означало, что контекст оставался «активным». И во время моего второго, третьего, четвертого и т. Д. c вызовов на конечную точку /token я заметил в наблюдателе, что контекстные вызовы накапливались (то есть он сохранял предыдущие изменения в контексте, которые я сделал из предыдущих вызовов /token - они никогда не избавлялись!

К сожалению, для моей ситуации можно было обернуть любые контекстные вызовы, сделанные в запросе /token, в оператор using, так что я знал, что после завершения using он будет расположен правильно , И это, похоже, сработало:)

Так что, если у вас есть похожий проект, похожий на мой, или похожий на тот учебник, которым я поделился, вы можете столкнуться с этой проблемой.

...