Неверный запрос (400) при использовании аутентификации токена Web API из Angular JS
Я хочу установить аутентификацию токена Web API с помощью Angular JS в качестве клиента. Я очень новичок в этой концепции аутентификации токенов внутри Web API.
Я не хочу использовать таблицы по умолчанию удостоверений ASP.NET для добавления или аутентификации пользователя. У меня есть собственная база данных и таблица с именем "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".
Пожалуйста, помогите мне успешно выполнить сценарий аутентификации токена Web 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: Добавьте аутентификацию Web 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);
}
);;
}
}]);
Вот и все.