使用来自 Angular JS 的 Web API 令牌身份验证时出现错误请求 (400)
Posted
技术标签:
【中文标题】使用来自 Angular JS 的 Web API 令牌身份验证时出现错误请求 (400)【英文标题】:Bad Request (400) when using Web API Token Authentication from Angular JS 【发布时间】:2017-12-05 01:29:02 【问题描述】:我想以 Angular JS 作为客户端建立 Web API 令牌认证。我对 Web API 中的令牌身份验证这个概念非常陌生。
我不想使用 ASP.NET Identity 默认表来添加或验证用户。我有自己的数据库和一个名为“EmployeeAccess”的表,其中包含 EmployeeNumber 作为用户 ID 和密码。我想根据此表中的值对用户进行身份验证,然后想授予令牌,以便他们获得后续调用的授权。我已经使用了所有必需的 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 代码时,我发现“AuthorizationServerProvider.cs”中的方法“GrantResourceOwnerCredentials()”永远不会被执行。错误消息出现在此之前。只有方法“ValidateClientAuthentication”和“MatchEndpoint”被执行。
请帮我成功运行Web API Token Authentication的场景。如果发现任何代码是多余的,请告诉我,以便我删除它。
【问题讨论】:
【参考方案1】:好的,这将是一个很长的答案,但请坚持到最后:)
第 1 步:删除 Global.asax
在 Owin 管道上运行时不需要 Global.asax。 Startup.cs 就是我所说的 Owins Global.asax。 它们基本上满足相同的目的,所以继续删除它。
第 2 步:删除 WebApiConfig.cs 中的 Cors 处理
不需要此代码,因为您已经在 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 和不记名令牌认证添加到 Startup.cs 中的 Owin 管道
您无需在 Global.asax 中绑定 WebApiConfig,而是将其附加到管道。 还将不记名令牌处理应用于管道。
您的 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,它基本上是一个字符串。 我还添加了 promise 而不是 $q 所做的回调。 IMO 我认为这个承诺读起来更清楚
提示:不要以明文形式发送凭据。 您可以使用 btoa(password) 将它们解码为 Base64 字符串,然后在后端对其进行解码。
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);
);;
]);
就是这样。
【讨论】:
感谢 Marcus 如此详尽地编写了您的答案,这有助于像我这样的新手获得基本知识。我希望我能给你+10,但该网站不允许..:-) @user1843970 Np,很高兴为您提供帮助!祝你有美好的一天 感谢您的 SetContextHeaders 方法!没有它,数据错误消息为空。但参数的正确类型是:private void SetContextHeaders(OAuthGrantResourceOwnerCredentialsContext context) @MarcusHöglund 我面临同样的错误请求错误,但仅当登录不正确时。当登录正确时,一切正常(幸运的是)。我正在使用敲除将登录请求发送到 /Token url。对此有任何提示吗?为什么它仅在无效登录时不起作用? @jstuardo 好的,您的登录模型上有任何 DataAnnotations 吗?如果是,那么确保用户在不满足这些要求的情况下不能从客户端发帖。如果没有,则在 GrantResourceOwnerCredentials 中设置断点并查看发生了什么以上是关于使用来自 Angular JS 的 Web API 令牌身份验证时出现错误请求 (400)的主要内容,如果未能解决你的问题,请参考以下文章
404 在 CORS 请求中返回,来自 Angular 服务的一些 Web api 调用
Web API系列教材1.3 — 实战:用ASP.NET Web API和Angular.js创建单页面应用程序(下)
Angular2 中的 Http PUT 到 .NET Core Web API 提供来自预检请求的 http 401 错误
Web API 2 入门——使用ASP.NET Web API和Angular.js构建单页应用程序(SPA)(谷歌翻译)