遵循 Owin OAuth 指南实现了以下authorization code flow
。我已成功从服务器接收授权代码,即。直到文章中提到的步骤D。
当我使用grant_type=authorization_code
从服务器请求access_token
时.我收到400 错误请求错误,说invalid grant
代码如下:
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
//use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
AuthorizeEndpointPath = new PathString("/oauth/authorize"),
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(60),
Provider = new SimpleOAuthProvider(),
AuthorizationCodeProvider = new AuthenticationCodeProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = ""
//});
}
授权代码提供程序
internal class AuthenticationCodeProvider: IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var code = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var token = new AuthCode()
{
Token = HashProvider.GetHash(code),
IssuedUtc = DateTime.UtcNow
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddAuthCodeToken(token);
if (result)
{
context.SetToken(token.Token);
}
}
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
if (headerPresent)
{
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
string hashedToken = context.Token;
hashedToken = Uri.UnescapeDataString(hashedToken);
using (AuthRepository _repo = new AuthRepository())
{
var authCode = await _repo.FindAuthCodeToken(hashedToken);
if (authCode != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(authCode.ProtectedTicket);
var result = await _repo.RemoveAuthCodeToken(hashedToken);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
OAuthAuthorizationServerProvider
public class SimpleOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
//Check if client details are valid
string clientId = string.Empty;
string clientSecret = string.Empty;
AuthClient client = null;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
//Remove the comments from the below line context.SetError, and invalidate context
//if you want to force sending clientId/secrects once obtain access tokens.
context.Validated();
//context.SetError("invalid_clientId", "ClientId should be sent.");
return Task.FromResult<object>(null);
}
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
if (client.Type == (Int16)ClientTypeEnum.Native)
{
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_clientId", "Client secret should be sent.");
return Task.FromResult<object>(null);
}
else
{
if (client.Secret != clientSecret)
{
context.SetError("invalid_clientId", "Client secret is invalid.");
return Task.FromResult<object>(null);
}
}
}
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return Task.FromResult<object>(null);
}
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
if (headerPresent)
{
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
using (AuthRepository _repo = new AuthRepository())
{
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("unconfirmed_email", "Please confirm your email id.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("FullName", user.FullName));
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("sub", context.UserName));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"session_id", Guid.NewGuid().ToString("n")
},
{
"email", user.Email
},
{
"user_id", user.Id.ToString()
},
{
"fullname", user.FullName.ToString()
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
//var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
//if (newClaim != null)
//{
// newIdentity.RemoveClaim(newClaim);
//}
//newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
//Authorization Code flow
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
string clientId = context.ClientId;
AuthClient client = null;
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
context.Validated(context.RedirectUri);
return Task.FromResult<object>(null);
}
public override async Task GrantClientCredentials(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);
}
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
//Generate auth code to be sent to oauth/token endpoint for getting access_token
if (context.AuthorizeRequest.IsImplicitGrantType)
{
var identity = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(identity);
context.RequestCompleted();
}
else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
{
var redirectUri = context.Request.Query["redirect_uri"];
var clientId = context.Request.Query["client_id"];
var userId = context.Request.Query["user_id"];
var state = context.Request.Query["state"];
var scope = context.Request.Query["scope"];
var sessionId = Guid.NewGuid().ToString("n");
var identity = new ClaimsIdentity(new GenericIdentity(
clientId, OAuthDefaults.AuthenticationType));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToString()));
var authorizeCodeContext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{"client_id", clientId},
{"user_id",userId },
{"session_id", sessionId },
{"redirect_uri", redirectUri}
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
}));
await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
//Set Cors
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
if (headerPresent)
{
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var model = new { RedirectUri = redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token) + "&state=" + state + "&scope=" + scope };
var json = Newtonsoft.Json.JsonConvert.SerializeObject(model);
context.Response.Write(json);
context.RequestCompleted();
}
}
/// <summary>
/// Verify the request for authorization_code
/// </summary>
public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
if ((context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType))
{
context.Validated();
}
else
{
context.Rejected();
}
}
//public override async Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
//{
// var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
// if (allowedOrigin == null) allowedOrigin = "*";
// var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
// if (headerPresent)
// {
// context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
// }
// context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
// var userIdTemp = context.Ticket.Identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
// if(userIdTemp == null)
// {
// }
// long userId = Convert.ToInt64(userIdTemp.Value);
// var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
// ApplicationUser user = await userManager.FindByIdAsync(userId);
// var identity = new ClaimsIdentity(context.Options.AuthenticationType);
// identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
// identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
// identity.AddClaim(new Claim("FullName", user.FullName));
// identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
// //identity.AddClaim(new Claim("sub", context.UserName));
// var props = new AuthenticationProperties(new Dictionary<string, string>
// {
// //{
// // "client_id", (context.Ticket.Properties.Dictionary.FirstOrDefault.ClientId == null) ? string.Empty : context.ClientId
// //},
// {
// "session_id", Guid.NewGuid().ToString("n")
// },
// {
// "email", user.Email
// },
// {
// "user_id", user.Id.ToString()
// },
// {
// "fullname", user.FullName.ToString()
// }
// });
// var ticket = new AuthenticationTicket(identity, props);
// context.Validated(ticket);
//}
}
似乎中间件会检查密钥redirect_uri
是否存在于AuthenticationProperties
字典中,将其删除,一切正常(具有经过验证的上下文(。
AuthorizationCodeProvider
woubld 的简化示例如下所示:
public class AuthorizationCodeProvider:AuthenticationTokenProvider {
public override void Create(AuthenticationTokenCreateContext context) {
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context) {
context.DeserializeTicket(context.Token);
context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
}
}
并且不要忘记在重写的方法中验证上下文OAuthAuthorizationServerProvider.ValidateClientAuthentication
.同样,下面是一个从模板项目的ApplicationOAuthProvider
类继承的简化示例:
public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
if(null!=context.RedirectUri) {
context.Validated(context.RedirectUri);
return Task.CompletedTask;
}
return base.ValidateClientRedirectUri(context);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
// Specify the actual expected client id and secret in your case
if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {
context.Validated(); // <-
return Task.CompletedTask;
}
}
return base.ValidateClientAuthentication(context);
}
public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
}
}
请注意,如果使用特定的客户端 ID 调用context.Validated
,则必须在票证的属性中放置相同的client_id,您可以使用方法AuthenticationTokenProvider.Receive