Отсутствует заголовок запроса «Авторизация» после вызова GrantResourceOwnerCredentials при попытке аутентификации с помощью OWIN из метода действия - PullRequest
0 голосов
/ 18 февраля 2020

Это беспокоило меня уже около суток. Я прочитал различные статьи и попробовал образцы и предложения, чтобы решить это. Я нашел решение, но не думаю, что оно «правильное». Я ищу совет о том, чего мне не хватает, и правильно все делаю.

Я экспериментирую с различными типами методов аутентификации в простом ASP. NET MVC приложении с использованием простого в база данных памяти. Одним из таких методов является аутентификация через OWIN с использованием простой формы имени пользователя / пароля. Все отлично работает, когда я проверяю это с почтальоном. С почтальоном я могу напрямую позвонить на конечную точку /auth и получить токен на предъявителя. Содержание тела, которое я использую, составляет grant_type=password&username=administrator&roleName=Admin&permissionLevel=2. roleName и permissionLevel - это функциональность, определяемая приложением c. Когда я добавляю заголовок Authorization с выданным токеном Bearer в запрос GET с помощью Postman и вызываю метод действия, украшенный атрибутом [Authorize], все прекрасно работает, и я успешно аутентифицируюсь с заголовком Authorization, содержащим токен Bearer.

Проблема возникает, когда я пытаюсь сделать то же самое из моего метода действия public async Task<ActionResult> OAuth(AppModels.User logonUser), который я использую для поиска пользователя в базе данных и выполнения перенаправления / вызова того же представления оттуда после вызова /auth конечная точка.

Я могу вызвать конечную точку токена, и переопределения ValidateClientAuthentication и GrantResourceOwnerCredentials вызываются, как и ожидалось, создается объект IIdentity и возвращается токен.

Но когда я пытаюсь выполнить перенаправление на другое действие или вызвать мой View в конце этого метода действия, ClaimsPrincipal и содержащиеся в нем утверждения, похоже, теряются, и я получаю HTTP 401 неавторизованным. Это потому, что мой запрос не содержит правильный заголовок запроса авторизации. Быстрое исправление, которое я обнаружил, было создание ClaimsPrincipal в переопределении GrantResourceOwnerCredentials, сохраните его в свойство stati c в Startup.cs и добавьте этот принципал в конвейер позже, используя метод Invoke. Тогда и только тогда я смогу заставить аутентификацию работать.

Внедрение заголовка запроса авторизации не работает в методе Invoke, так как я думаю, что это нужно делать в коде клиента. Заголовок запроса должен быть уже при вызове метода Invoke. Но я не хочу делать это в коде клиента javascript / angular / some-framework. Я также не хочу использовать куки, я хочу использовать чистый заголовок, и почтальон показывает, что это возможно.

Поэтому мне нужно вводить заголовок авторизации, содержащий токен носителя, при каждом запросе, используя ASP. NET MVC только, но я не знаю, как. Я подозреваю, что у меня здесь ситуация с курицей и яйцом, и единственный способ добавить заголовок запроса - добавить клиентский код javascript на мой взгляд, и сделайте это оттуда. Различные примеры, которые я нашел в Интернете, не помогают мне, поскольку все они связаны с использованием Postman или аналогичного инструмента, а не с реальной ситуацией, называющей UseOAuthAuthorizationServer и UseOAuthBearerAuthentication. Пожалуйста, совет.

Вот мой код OWIN в Startup.cs классе:

        public void ConfigureOAuth(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = false,
                TokenEndpointPath = new PathString("/auth"),
                AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
                Provider = new AuthorizationServerProvider(),
                AuthenticationMode =  AuthenticationMode.Active
            };

            // Token Generation
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions { Provider = new OAuthBearerAuthenticationProvider()  });
            app.UseOAuthAuthorizationServer(OAuthServerOptions);

            //my dirty fix
            app.Use<PrincipalInjectionMiddleware>();

            HttpConfiguration configuration = new HttpConfiguration();
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            app.UseWebApi(configuration);
        }

Это код OAuthAuthorizationServerProvider класса

      public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
        {
            public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            {   
                context.Validated();
                base.ValidateClientAuthentication(context);
            }

            public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
            {
                FormCollection dataSet = context.OwinContext.Environment["Microsoft.Owin.Form#collection"] as FormCollection;

                var roleName = dataSet["roleName"];
                var permissionLevel = dataSet["permissionLevel"];
                var userName = dataSet["userName"];

                //TODO:Configure CORS
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

                //grant claims here
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim(ClaimTypes.Name, userName));
                identity.AddClaim(new Claim(ClaimTypes.Role, roleName));
                identity.AddClaim(new Claim(ExtendedClaimTypes.PermissionLevel, permissionLevel));

                ClaimsPrincipal principal = new ClaimsPrincipal(identity);

                //my dirty fix
                Startup.issuedPrincipal = principal;

                context.OwinContext.Authentication.SignIn(new AuthenticationProperties { IsPersistent = true, AllowRefresh = true }, identity);

                context.Validated(identity);

                base.GrantResourceOwnerCredentials(context);
            }

Это это код для PrincipalInjectionMiddleware

        public class PrincipalInjectionMiddleware : OwinMiddleware
        {
            public PrincipalInjectionMiddleware(OwinMiddleware next) : base(next)
            {

            }

            public async override Task Invoke(IOwinContext context)
            {   
                var principal = Startup.issuedPrincipal;

                if(principal != null)
                {
                    context.Authentication.User = Startup.issuedPrincipal;
                    context.Request.User = Startup.issuedPrincipal;
                }

                await Next.Invoke(context);
            }
        }

Вот мой код метода действия после того, как я отправил форму с именем пользователя / паролем:

       [HttpPost]
        public async Task<ActionResult> OAuth(AppModels.User logonUser)
        {
            string tokenAuthPath = "mvcsandbox/auth";
            ViewData["StatusMessage"] = String.Empty;

            using (var dbContext = new SandboxContext())
            {
                var dbUser = dbContext.Users.Include("UserRole").SingleOrDefault(user => user.Name == logonUser.Name && user.Password == logonUser.Password);
                string bodyContent = String.Format("grant_type=password&userName={0}&roleName={1}&permissionLevel={2}&password={3}", dbUser.Name, dbUser.UserRole.Name, ((int)dbUser.UserRole.PermissionLevel).ToString(), dbUser.Password);

                if (dbUser != null)
                {
                    await Task.Run(() =>
                    {
                        HttpResponseMessage output = _serviceClient.GetServiceClient().PostAsync(tokenAuthPath, new StringContent(bodyContent)).Result;
                        /*HttpContent content = output.Content;
                        string formattedContent = content.ReadAsStringAsync().Result;
                        dynamic contentObject = System.Web.Helpers.Json.Decode(formattedContent);*/
                    });
                    return RedirectToAction("Main");
                }
                else
                {
                    ViewData["StatusMessage"] = "Invalid username/password, login failed.";
                    return View();
                }
            }
        }

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...