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",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,x))); context.Validated(identity); return Task.FromResult(0); } private readonly ConcurrentDictionary<string,string> _authenticationCodes = new ConcurrentDictionary<string,string>(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); } } }
解决方法
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()。请注意,被拒绝和SetError方法的定义是:
拒绝:
Marks this context as not validated by the application. IsValidated and HasError become false as a result of calling.
SETERROR:
Marks this context as not validated by the application and assigns varIoUs error information properties. HasError becomes true and IsValidated becomes false as a result of calling.
再次,通过在设置错误后调用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); } } }
最后,正如Jeff指出的那样,您必须在Startup.Configuration或Startup.ConfigureAuth方法中注册此自定义OwinMiddleware:
app.Use<CustomAuthenticationMiddleware>();
使用上述解决方案,您现在可以设置状态代码和自定义错误消息,如下所示:
>无效的用户名或密码
>此帐户已超过最大尝试次数
>电子邮件帐户尚未确认
在客户端应用程序中,需要捕获并处理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。另外,我没有讨论错误处理,所以你需要确保在您的解决方案中实施适当的错误处理。)