如何在客户端获取DotNetOpenAuth.OAuth2返回的错误消息?

 郭城镣 发布于 2022-12-31 15:14

我正在使用ExchangeUserCredentialForToken函数从授权服务器获取令牌.当我的用户存在于我的数据库中时,它工作正常,但是当凭据是incorect时,我想向客户端发回消息.我正在使用以下2行代码来设置错误消息:

context.SetError("Autorization Error", "The username or password is incorrect!");
context.Rejected();

但在客户端,我只得到协议错误(错误400).您能帮我解决一下如何在授权服务器上获取服务器端设置的错误消息?

来自授权服务器的完整应用程序配置:

using Constants;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using AuthorizationServer.Entities;
using AuthorizationServer.Entities.Infrastructure.Abstract;
using AuthorizationServer.Entities.Infrastructure.Concrete;

namespace AuthorizationServer
{
    public partial class Startup
    {
        private IEmployeeRepository Repository;  
        public void ConfigureAuth(IAppBuilder app)
        {
            //instanciate the repository
            Repository = new EmployeeRepository();

            // Enable Application Sign In Cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Application",
                AuthenticationMode = AuthenticationMode.Passive,
                LoginPath = new PathString(Paths.LoginPath),
                LogoutPath = new PathString(Paths.LogoutPath),
            });

            // Enable External Sign In Cookie
            app.SetDefaultSignInAsAuthenticationType("External");
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "External",
                AuthenticationMode = AuthenticationMode.Passive,
                CookieName = CookieAuthenticationDefaults.CookiePrefix + "External",
                ExpireTimeSpan = TimeSpan.FromMinutes(5),
            });

            // Enable google authentication
            app.UseGoogleAuthentication();

            // Setup Authorization Server
            app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
            {
                AuthorizeEndpointPath = new PathString(Paths.AuthorizePath),
                TokenEndpointPath = new PathString(Paths.TokenPath),
                ApplicationCanDisplayErrors = true,
#if DEBUG
                AllowInsecureHttp = true,
#endif
                // Authorization server provider which controls the lifecycle of Authorization Server
                Provider = new OAuthAuthorizationServerProvider
                {
                    OnValidateClientRedirectUri = ValidateClientRedirectUri,
                    OnValidateClientAuthentication = ValidateClientAuthentication,
                    OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials,
                    OnGrantClientCredentials = GrantClientCredetails
                },

                // Authorization code provider which creates and receives authorization code
                AuthorizationCodeProvider = new AuthenticationTokenProvider
                {
                    OnCreate = CreateAuthenticationCode,
                    OnReceive = ReceiveAuthenticationCode,
                },

                // Refresh token provider which creates and receives referesh token
                RefreshTokenProvider = new AuthenticationTokenProvider
                {
                    OnCreate = CreateRefreshToken,
                    OnReceive = ReceiveRefreshToken,
                }
            });

            // indicate our intent to use bearer authentication
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
            {
                AuthenticationType = "Bearer",
                AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active
            });
        }

        private Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == Clients.Client1.Id)
            {
                context.Validated(Clients.Client1.RedirectUrl);
            }
            else if (context.ClientId == Clients.Client2.Id)
            {
                context.Validated(Clients.Client2.RedirectUrl);
            }
            return Task.FromResult(0);
        }

        private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {

            string clientname;
            string clientpassword;


            if (context.TryGetBasicCredentials(out clientname, out clientpassword) ||
                context.TryGetFormCredentials(out clientname, out clientpassword))
            {
                employee Employee = Repository.GetEmployee(clientname, clientpassword);

                if (Employee != null)
                {
                    context.Validated();
                }
                else
                {
                    context.SetError("Autorization Error", "The username or password is incorrect!");
                    context.Rejected();
                }
            }
            return Task.FromResult(0);
        }

        private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));

            context.Validated(identity);

            return Task.FromResult(0);
        }

        private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
        {
            var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));

            context.Validated(identity);

            return Task.FromResult(0);
        }


        private readonly ConcurrentDictionary _authenticationCodes =
            new ConcurrentDictionary(StringComparer.Ordinal);

        private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
        {
            context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
            _authenticationCodes[context.Token] = context.SerializeTicket();
        }

        private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
        {
            string value;
            if (_authenticationCodes.TryRemove(context.Token, out value))
            {
                context.DeserializeTicket(value);
            }
        }

        private void CreateRefreshToken(AuthenticationTokenCreateContext context)
        {
            context.SetToken(context.SerializeTicket());
        }

        private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
        {
            context.DeserializeTicket(context.Token);
        }
    }
}

小智.. 32

经过几个小时的网络搜索和阅读blob以及owin文档后,我找到了一种方法来返回401以进行失败的登录尝试.

我意识到添加下面的标题有点像黑客,但我找不到任何方法来读取IOwinContext.Response.Body流来查找错误消息.

首先,在OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials我使用SetError()并添加了一个Headers响应

context.SetError("Autorization Error", "The username or password is incorrect!");
context.Response.Headers.Add("AuthorizationResponse", new[] { "Failed" });

现在,您可以通过一种方法来区分失败的身份验证请求的400错误和其他原因导致的400错误.

下一步是创建一个继承的类OwinMiddleware.此类检查传出响应,如果StatusCode == 400存在上面的Header,它会将StatucCode更改为401.

public class InvalidAuthenticationMiddleware : OwinMiddleware
{
    public InvalidAuthenticationMiddleware(OwinMiddleware next) 
        : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        await Next.Invoke(context);

        if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey("AuthorizationResponse"))
        {
            context.Response.Headers.Remove("AuthorizationResponse");
            context.Response.StatusCode = 401;
        }
    }
}

最后要做的是在你的Startup.Configuration方法中,注册你刚刚创建的类.我之前在方法中做了其他任何事情之前注册了它.

app.Use();

如果我在OAuth配置之前添加了`app.Use ();`行,它对我有用. (8认同)


Rogala.. 25

这是一个完整的解决方案,将Jeff的概念与我的原始帖子结合使用.

1)在上下文中设置错误消息

如果在设置错误消息后调用了context.Rejected(),则会删除错误消息(请参阅下面的示例):

    context.SetError("Account locked", 
             "You have exceeded the total allowed failed logins.  Please try back in an hour.");
    context.Rejected();

您将需要从任务中删除context.Rejected().请注意Rejected和SetError方法的定义是:

被拒绝:

将此上下文标记为未由应用程序验证.由于调用,IsValidated和HasError变为false.

SETERROR:

将此上下文标记为未由应用程序验证,并分配各种错误信息属性.HasError变为true,并且IsValidated因调用而变为false.

同样,通过在设置错误后调用Rejected方法,上下文将被标记为没有错误,并且将删除错误消息.

2)设置响应的状态代码: 使用Jeff的示例,稍微旋转一下.

我将创建一个全局属性来设置状态代码的标记,而不是使用魔术字符串.在静态全局类中,创建一个标记状态代码的属性(我使用了X-Challenge,但您当然可以使用您选择的任何内容.)这将用于标记响应中添加的标头属性.

public static class ServerGlobalVariables
{
//Your other properties...
public const string OwinChallengeFlag = "X-Challenge";
}

然后,在OAuthAuthorizationServerProvider的各种任务中,您将添加标记作为响应中新标头值的键.将HttpStatusCode枚举与您的全局标志结合使用,您将可以访问所有各种状态代码,并避免使用魔术字符串.

//Set the error message
context.SetError("Account locked", 
        "You have exceeded the total allowed failed logins.  Please try back in an hour.");

//Add your flag to the header of the response
context.Response.Headers.Add(ServerGlobalVariables.OwinChallengeFlag, 
         new[] { ((int)HttpStatusCode.Unauthorized).ToString() }); 

在客户OwinMiddleware中,您可以使用全局变量在标头中搜索标志:

//This class handles all the OwinMiddleware responses, so the name should 
//not just focus on invalid authentication
public class CustomAuthenticationMiddleware : OwinMiddleware
{
    public CustomAuthenticationMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        await Next.Invoke(context);

        if (context.Response.StatusCode == 400 
            && context.Response.Headers.ContainsKey(
                      ServerGlobalVariables.OwinChallengeFlag))
        {
            var headerValues = context.Response.Headers.GetValues
                  (ServerGlobalVariables.OwinChallengeFlag);

            context.Response.StatusCode = 
                   Convert.ToInt16(headerValues.FirstOrDefault());

            context.Response.Headers.Remove(
                   ServerGlobalVariables.OwinChallengeFlag);
        }         

    }
}

最后,正如杰夫指出,你在你注册这个定制OwinMiddleware Startup.ConfigurationStartup.ConfigureAuth方法:

app.Use();

使用上述解决方案,您现在可以设置状态代码和自定义错误消息,如下所示:

用户名或密码无效

此帐户已超过最大尝试次数

电子邮件帐户尚未确认

3)从ProtocolException中提取错误消息

在客户端应用程序中,需要捕获并处理ProtocolException.这样的东西会给你答案:

//Need to create a class to deserialize the Json
//Create this somewhere in your application
public class OAuthErrorMsg
    {
        public string error { get; set; }
        public string error_description { get; set; }
        public string error_uri { get; set; }
    }

 //Need to make sure to include Newtonsoft.Json
 using Newtonsoft.Json;

 //Code for your object....

 private void login()
    {
        try
        {
            var state = _webServerClient.ExchangeUserCredentialForToken(
                this.emailTextBox.Text, 
                this.passwordBox.Password.Trim(), 
                scopes: new string[] { "PublicProfile" });

            _accessToken = state.AccessToken;
            _refreshToken = state.RefreshToken;
        }
        catch (ProtocolException ex)
        {
            var webException = ex.InnerException as WebException;

            OAuthErrorMsg error = 
                JsonConvert.DeserializeObject(
                ExtractResponseString(webException));

            var errorMessage = error.error_description;
            //Now it's up to you how you process the errorMessage
        }
    }

    public static string ExtractResponseString(WebException webException)
    {
        if (webException == null || webException.Response == null)
            return null;

        var responseStream = 
            webException.Response.GetResponseStream() as MemoryStream;

        if (responseStream == null)
            return null;

        var responseBytes = responseStream.ToArray();

        var responseString = Encoding.UTF8.GetString(responseBytes);
        return responseString;
    }

我测试了这个,它在VS2013 Pro 4.5中完美运行!

(请注意,我没有包含所有必需的命名空间或附加代码,因为这将根据应用程序而有所不同:WPF,MVC或Winform.此外,我没有讨论错误处理,所以你需要确保在整个解决方案中实施适当的错误处理.

2 个回答
  • 这是一个完整的解决方案,将Jeff的概念与我的原始帖子结合使用.

    1)在上下文中设置错误消息

    如果在设置错误消息后调用了context.Rejected(),则会删除错误消息(请参阅下面的示例):

        context.SetError("Account locked", 
                 "You have exceeded the total allowed failed logins.  Please try back in an hour.");
        context.Rejected();
    

    您将需要从任务中删除context.Rejected().请注意Rejected和SetError方法的定义是:

    被拒绝:

    将此上下文标记为未由应用程序验证.由于调用,IsValidated和HasError变为false.

    SETERROR:

    将此上下文标记为未由应用程序验证,并分配各种错误信息属性.HasError变为true,并且IsValidated因调用而变为false.

    同样,通过在设置错误后调用Rejected方法,上下文将被标记为没有错误,并且将删除错误消息.

    2)设置响应的状态代码: 使用Jeff的示例,稍微旋转一下.

    我将创建一个全局属性来设置状态代码的标记,而不是使用魔术字符串.在静态全局类中,创建一个标记状态代码的属性(我使用了X-Challenge,但您当然可以使用您选择的任何内容.)这将用于标记响应中添加的标头属性.

    public static class ServerGlobalVariables
    {
    //Your other properties...
    public const string OwinChallengeFlag = "X-Challenge";
    }
    

    然后,在OAuthAuthorizationServerProvider的各种任务中,您将添加标记作为响应中新标头值的键.将HttpStatusCode枚举与您的全局标志结合使用,您将可以访问所有各种状态代码,并避免使用魔术字符串.

    //Set the error message
    context.SetError("Account locked", 
            "You have exceeded the total allowed failed logins.  Please try back in an hour.");
    
    //Add your flag to the header of the response
    context.Response.Headers.Add(ServerGlobalVariables.OwinChallengeFlag, 
             new[] { ((int)HttpStatusCode.Unauthorized).ToString() }); 
    

    在客户OwinMiddleware中,您可以使用全局变量在标头中搜索标志:

    //This class handles all the OwinMiddleware responses, so the name should 
    //not just focus on invalid authentication
    public class CustomAuthenticationMiddleware : OwinMiddleware
    {
        public CustomAuthenticationMiddleware(OwinMiddleware next)
            : base(next)
        {
        }
    
        public override async Task Invoke(IOwinContext context)
        {
            await Next.Invoke(context);
    
            if (context.Response.StatusCode == 400 
                && context.Response.Headers.ContainsKey(
                          ServerGlobalVariables.OwinChallengeFlag))
            {
                var headerValues = context.Response.Headers.GetValues
                      (ServerGlobalVariables.OwinChallengeFlag);
    
                context.Response.StatusCode = 
                       Convert.ToInt16(headerValues.FirstOrDefault());
    
                context.Response.Headers.Remove(
                       ServerGlobalVariables.OwinChallengeFlag);
            }         
    
        }
    }
    

    最后,正如杰夫指出,你在你注册这个定制OwinMiddleware Startup.ConfigurationStartup.ConfigureAuth方法:

    app.Use<CustomAuthenticationMiddleware>();
    

    使用上述解决方案,您现在可以设置状态代码和自定义错误消息,如下所示:

    用户名或密码无效

    此帐户已超过最大尝试次数

    电子邮件帐户尚未确认

    3)从ProtocolException中提取错误消息

    在客户端应用程序中,需要捕获并处理ProtocolException.这样的东西会给你答案:

    //Need to create a class to deserialize the Json
    //Create this somewhere in your application
    public class OAuthErrorMsg
        {
            public string error { get; set; }
            public string error_description { get; set; }
            public string error_uri { get; set; }
        }
    
     //Need to make sure to include Newtonsoft.Json
     using Newtonsoft.Json;
    
     //Code for your object....
    
     private void login()
        {
            try
            {
                var state = _webServerClient.ExchangeUserCredentialForToken(
                    this.emailTextBox.Text, 
                    this.passwordBox.Password.Trim(), 
                    scopes: new string[] { "PublicProfile" });
    
                _accessToken = state.AccessToken;
                _refreshToken = state.RefreshToken;
            }
            catch (ProtocolException ex)
            {
                var webException = ex.InnerException as WebException;
    
                OAuthErrorMsg error = 
                    JsonConvert.DeserializeObject<OAuthErrorMsg>(
                    ExtractResponseString(webException));
    
                var errorMessage = error.error_description;
                //Now it's up to you how you process the errorMessage
            }
        }
    
        public static string ExtractResponseString(WebException webException)
        {
            if (webException == null || webException.Response == null)
                return null;
    
            var responseStream = 
                webException.Response.GetResponseStream() as MemoryStream;
    
            if (responseStream == null)
                return null;
    
            var responseBytes = responseStream.ToArray();
    
            var responseString = Encoding.UTF8.GetString(responseBytes);
            return responseString;
        }
    

    我测试了这个,它在VS2013 Pro 4.5中完美运行!

    (请注意,我没有包含所有必需的命名空间或附加代码,因为这将根据应用程序而有所不同:WPF,MVC或Winform.此外,我没有讨论错误处理,所以你需要确保在整个解决方案中实施适当的错误处理.

    2022-12-31 15:15 回答
  • 经过几个小时的网络搜索和阅读blob以及owin文档后,我找到了一种方法来返回401以进行失败的登录尝试.

    我意识到添加下面的标题有点像黑客,但我找不到任何方法来读取IOwinContext.Response.Body流来查找错误消息.

    首先,在OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials我使用SetError()并添加了一个Headers响应

    context.SetError("Autorization Error", "The username or password is incorrect!");
    context.Response.Headers.Add("AuthorizationResponse", new[] { "Failed" });
    

    现在,您可以通过一种方法来区分失败的身份验证请求的400错误和其他原因导致的400错误.

    下一步是创建一个继承的类OwinMiddleware.此类检查传出响应,如果StatusCode == 400存在上面的Header,它会将StatucCode更改为401.

    public class InvalidAuthenticationMiddleware : OwinMiddleware
    {
        public InvalidAuthenticationMiddleware(OwinMiddleware next) 
            : base(next)
        {
        }
    
        public override async Task Invoke(IOwinContext context)
        {
            await Next.Invoke(context);
    
            if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey("AuthorizationResponse"))
            {
                context.Response.Headers.Remove("AuthorizationResponse");
                context.Response.StatusCode = 401;
            }
        }
    }
    

    最后要做的是在你的Startup.Configuration方法中,注册你刚刚创建的类.我之前在方法中做了其他任何事情之前注册了它.

    app.Use<InvalidAuthenticationMiddleware>();
    

    2022-12-31 15:15 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有