Плохой запрос (400) при использовании аутентификации токена веб-API из Angular JS

Я хочу установить аутентификацию токена веб-API с Angular JS в качестве клиента. Я очень новичок в этой концепции аутентификации токена внутри веб-API.

Я не хочу использовать таблицы ASP.NET Identity по умолчанию для добавления или аутентификации пользователя. У меня есть собственная база данных и таблица под названием «EmployeeAccess», которая содержит EmployeeNumber в качестве идентификатора пользователя и пароля. Я хочу аутентифицировать пользователей по значениям в этой таблице, а затем хочу предоставить токен, чтобы они были авторизованы для последующего вызова. Я использовал все необходимые ссылки OWIN и ASP.NET для достижения результата. Вот мой код разных компонентов: -

Global.asax

public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
           // AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);

        }

        protected void Application_BeginRequest()
        {
            if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
            {
                // Cache the options request.
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", HttpContext.Current.Request.Headers["Origin"]);
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, PUT, DELETE, POST, OPTIONS");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
                HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
                HttpContext.Current.Response.End();
            }
        }
    }

WebApiConfig.cs

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {            
            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
            config.Formatters.Remove(config.Formatters.XmlFormatter);

            var cors = new EnableCorsAttribute("*", "*", "*");
            config.EnableCors(cors);
        }
    }

Startup.cs

[assembly: OwinStartup(typeof(Application.WebAPI.Startup))]

namespace Application.WebAPI
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            var myProvider = new AuthorizationServerProvider();
            OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/Token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = myProvider
            };
            app.UseOAuthAuthorizationServer(options);
        }
    }
}

AuthorizationServerProvider.cs

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

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            //context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            string userId = context.UserName;
            string password = context.Password;

            EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
            EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);

            if(vmEmployeeAccess != null)
            {
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
                context.Validated(identity);
            }
            else
            {
                context.SetError("invalid_grant", "Provided username and password is incorrect");
                return;
            }
        }               
    } 

Login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js"></script>
    <script src="../Scripts/AngularControllers/LoginController.js"></script>
    <script src="../Scripts/AngularServices/ApiCallService.js"></script>
</head>
<body ng-app="appHome">
    <div ng-controller="ctrlLogin">
        <label>Employee Number</label>
        <input type="text" id="txtEmpNumber" ng-model="md_empnumber" />
        <br/>
        <br/>
        <label>Password</label>
        <input type="text" id="txtEmpNumber" ng-model="md_password"  />

        <button id="btnAdd" type="submit" ng-click="Login()">Login</button>
    </div>
</body>
</html>

LoginController.js

var myApp = angular.module('appHome', []);
myApp.controller("ctrlLogin", ['$scope', 'MetadataOrgFactory', '$location', function ($scope, MetadataOrgFactory, $location) {
    $scope.Login = function () {
        var objLogin = {
            'username' : $scope.md_empnumber,
            'password' : $scope.md_password,
            'grant_type' : 'password'
        };

        MetadataOrgFactory.postLoginCall('Token', objLogin, function (dataSuccess) {
            alert("Welcome " + dataSuccess);           
        }, function (dataError) {
        });
    }
}]);

ApiCallService.js

var appService = angular.module('appHome');
appService.factory('MetadataOrgFactory', ['$http', function ($http) {

    var url = 'http://localhost:60544';    
    var dataFactory = {};    
    dataFactory.postLoginCall = function (controllerName, objData, callbackSuccess, callbackError) {

        $http.post(url + '/' + controllerName, objData,{headers:{ 'Content-Type': 'application/x-www-form-urlencoded' }}).then
            (function success(response) {
                alert("Success");
                callbackSuccess(response.data);
            }, function error(response) {
                callbackError(response.status);
            });
    };
    return dataFactory;
}])

Когда я нажимаю кнопку входа в систему, я получаю сообщение об ошибке ниже: -

POST http://localhost:60544/Token 400 (неверный запрос)

Когда я отлаживал код WebAPI, я обнаружил, что метод «GrantResourceOwnerCredentials ()» внутри «AuthorizationServerProvider.cs» никогда не запускается. Перед этим появляется сообщение об ошибке. Выполняются только методы ValidateClientAuthentication и MatchEndpoint.

Пожалуйста, помогите мне успешно запустить сценарий аутентификации токена веб-API. Если какой-либо код окажется лишним, дайте мне знать, чтобы я мог его удалить.




Ответы (1)


Хорошо, это будет длинный ответ, но держись до конца :)

Шаг 1. Удалите Global.asax

Global.asax не нужен при запуске в конвейере Owin. Startup.cs - это то, что я бы назвал Owins Global.asax. По сути, они служат для одной и той же цели, так что удалите ее.

Шаг 2. Удалите обработку Cors в файле WebApiConfig.cs

Этот код не нужен, поскольку вы уже объявили его в Startup.cs.

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

Ваш WebApiConfig.cs будет выглядеть так

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {            
        // Web API routes
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
        config.Formatters.Remove(config.Formatters.XmlFormatter);
    }
}

Шаг 3. Добавьте веб-API и аутентификацию токена-носителя в конвейер Owin в Startup.cs

Вместо того, чтобы связывать WebApiConfig в Global.asax, вы присоединяете его к конвейеру. Также примените к конвейеру обработку токена-носителя.

Ваш Startup.cs будет выглядеть так

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/Token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new AuthorizationServerProvider()
        };
        app.UseOAuthAuthorizationServer(options);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        //Register the web api to the pipeline 
        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);
        app.UseWebApi(config);
    }
}

Шаг 4. Добавьте заголовки к запросу в AuthorizationServerProvider.cs

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated(); 
    }
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        SetContextHeaders(context);
        string userId = context.UserName;
        string password = context.Password;

        EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
        EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);

        if(vmEmployeeAccess != null)
        {
            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
            context.Validated(identity);
        }
        else
        {
            context.SetError("invalid_grant", "Provided username and password is incorrect");
            return;
        }
    }
    private void SetContextHeaders(IOwinContext context)
    {
        context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
        context.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "GET, PUT, DELETE, POST, OPTIONS" });
        context.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Content-Type, Accept, Authorization" });
        context.Response.Headers.Add("Access-Control-Max-Age", new[] { "1728000" });
    }
} 

Шаг 5. Сделайте правильный запрос к серверу Oauth

Запрос к серверу oauth должен иметь тип содержимого x-www-form-urlencoded, который в основном является строкой. Я также добавил обещание вместо обратных вызовов, что и делает $ q. ИМО Я думаю, обещание более понятно читать

СОВЕТ: не отправляйте учетные данные открытым текстом. Вы можете закодировать их в строку Base64 с помощью btoa (пароль), а затем декодировать в своем внутреннем интерфейсе.

angular.module('appHome').factory('MetadataOrgFactory', ['$http', function ($http) {

    var url = 'http://localhost:60544';    
    var dataFactory = {};  
    dataFactory.login = function (userName, password) {
        var deferred = $q.defer();

        $http({
            method: 'POST',
            url: url + '/Token',
            processData: false,
            contentType: 'application/x-www-form-urlencoded',
            data: "grant_type=password&username=" + userName + "&password=" + password,
        }).
            success(function (data) {
                deferred.resolve(data);
            }).
            error(function (message, status) {              
                console.log(message);
                deferred.reject(message, status);
            });

        return deferred.promise;
    };  
    return dataFactory;
}]);

Шаг 6. Сделайте запрос на вход со своего контроллера

angular.module('appHome', []);
angular.module('appHome').controller("ctrlLogin", ['$scope', 'MetadataOrgFactory', '$location', function ($scope, MetadataOrgFactory, $location) {
    $scope.Login = function () {

        MetadataOrgFactory.postLoginCall($scope.md_empnumber, $scope.md_password).then(
            function (result) {
                //success
            },
                function (error, statusCode) {
                    console.log(error);
                }
            );;
    }
}]);

Вот и все.

person Marcus Höglund    schedule 01.07.2017
comment
Спасибо, Маркус, за столь подробный ответ, который помогает такому новичку, как я, получить базовые знания. Хотел бы я дать вам +10, но сайт не позволяет .. :-) - person simple user; 04.07.2017
comment
@ user1843970 Нп, с радостью помогу !! Хорошего дня - person Marcus Höglund; 04.07.2017
comment
Спасибо за метод SetContextHeaders! без него сообщение об ошибке данных будет пустым. Но правильный тип параметра: private void SetContextHeaders (контекст OAuthGrantResourceOwnerCredentialsContext) - person Damien JALLON; 29.05.2018
comment
@ MarcusHöglund Я сталкиваюсь с той же ошибкой неверного запроса, но только при неправильном входе в систему. Когда логин правильный, все работает отлично (к счастью). Я использую нокаут для отправки запроса входа на / Token url. Есть намёк на это? почему он не работает только при неверном входе в систему? - person jstuardo; 05.12.2018
comment
@jstuardo Хорошо, есть ли у вас какие-либо DataAnnotations для вашей модели входа? Если да, то убедитесь, что пользователи не могут публиковать сообщения от клиента, не выполнив эти требования. Если нет, установите точку останова в GrantResourceOwnerCredentials и посмотрите, что происходит. - person Marcus Höglund; 05.12.2018