使用来自 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 调用

使用 angular js 和 c# 使用 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)(谷歌翻译)

解答网友提问 | 使用VS2022快速生成React/Angular/Vue.js + Web API前后端集成项目