ASP.Net 身份 2 - 来自 OAuthAuthorizationServerProvider 的自定义响应
ASP.Net Identity 2 - custom response from OAuthAuthorizationServerProvider
这个问题是我上一个问题的延续:ASP.Net Identity 2 login using password from SMS - not using two-factor authentication
我构建了自定义 OAuthAuthorizationServerProvider 以支持自定义 grant_type。
我的想法是创建 sms
的 grant_type,这将允许用户生成一次性访问代码,该代码将发送到他的手机 phone,然后在使用 [= 发送请求时将用户作为密码42=] 的密码。
现在,在生成、存储并通过 SMS 发送密码后,我想 return 自定义响应,而不是来自我的 GrantCustomExtension 的令牌。
public override async Task GrantCustomExtension(OAuthGrantCustomExtensionContext context)
{
const string allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] {allowedOrigin});
if (context.GrantType != "sms")
{
context.SetError("invalid_grant", "unsupported grant_type");
return;
}
var userName = context.Parameters.Get("username");
if (userName == null)
{
context.SetError("invalid_grant", "username is required");
return;
}
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(userName);
if (user == null)
{
context.SetError("invalid_grant", "user not found");
return;
}
var generator = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
await userManager.UpdateSecurityStampAsync(user.Id);
var accessCode = await generator.GenerateAsync("SMS", userManager, user);
var accessCodeExpirationTime = TimeSpan.FromMinutes(5);
var result = await userManager.AddAccessCode(user, accessCode, accessCodeExpirationTime);
if(result.Succeeded)
{
Debug.WriteLine("Login code:"+accessCode);
//here I'll send login code to user phone via SMS
}
//return 200 (OK)
//with content type="application/json; charset=utf-8"
//and custom json content {"message":"code send","expires_in":300}
//skip part below
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "SMS");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
如何停止从 OAuthAuthorizationServerProvider 生成令牌和 return 自定义响应?
我知道两种方法:TokenEndpoint
、TokenEndpointResponse
,但我想覆盖整个响应,而不仅仅是令牌。
编辑:
现在,我正在使用以下代码在 GrantCustomExtension
中创建临时 ClaimsIdentity:
var ci = new ClaimsIdentity();
ci.AddClaim(new Claim("message","send"));
ci.AddClaim(new Claim("expires_in", accessCodeExpirationTime.TotalSeconds.ToString(CultureInfo.InvariantCulture)));
context.Validated(ci);
我正在覆盖 TokenEndpointResponse
:
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
if (context.TokenEndpointRequest.GrantType != "sms") return base.TokenEndpointResponse(context);
//clear response containing temporary token.
HttpContext.Current.Response.SuppressContent = true;
return Task.FromResult<object>(null);
}
这有两个问题:当我打电话给 context.Validated(ci);
时,我说这是一个有效的用户,但我想回复信息,我已经通过短信发送了访问代码。
HttpContext.Current.Response.SuppressContent = true;
清除响应,但我想 return 一些东西而不是空响应。
这更像是一种解决方法,而不是最终解决方案,但我相信这是解决问题的最可靠方法,无需重写默认 OAuthAuthorizationServerProvider
实现中的大量代码。
方法很简单:使用 Owin 中间件捕获令牌请求,如果发送了 SMS,则覆盖响应。
[评论后编辑] 修复了代码以允许根据此答案缓冲和更改响应主体
在您的 Startup.cs
文件中:
public void Configuration(IAppBuilder app)
{
var tokenPath = new PathString("/Token"); //the same path defined in OAuthOptions.TokenEndpointPath
app.Use(async (c, n) =>
{
//check if the request was for the token endpoint
if (c.Request.Path == tokenPath)
{
var buffer = new MemoryStream();
var body = c.Response.Body;
c.Response.Body = buffer; // we'll buffer the response, so we may change it if needed
await n.Invoke(); //invoke next middleware (auth)
//check if we sent a SMS
if (c.Get<bool>("sms_grant:sent"))
{
var json = JsonConvert.SerializeObject(
new
{
message = "code send",
expires_in = 300
});
var bytes = Encoding.UTF8.GetBytes(json);
buffer.SetLength(0); //change the buffer
buffer.Write(bytes, 0, bytes.Length);
//override the response headers
c.Response.StatusCode = 200;
c.Response.ContentType = "application/json";
c.Response.ContentLength = bytes.Length;
}
buffer.Position = 0; //reset position
await buffer.CopyToAsync(body); //copy to real response stream
c.Response.Body = body; //set again real stream to response body
}
else
{
await n.Invoke(); //normal behavior
}
});
//other owin middlewares in the pipeline
//ConfigureAuth(app);
//app.UseWebApi( .. );
}
在您的自定义授权方法中:
// ...
var result = await userManager.AddAccessCode(user, accessCode, accessCodeExpirationTime);
if(result.Succeeded)
{
Debug.WriteLine("Login code:"+accessCode);
//here I'll send login code to user phone via SMS
}
context.OwinContext.Set("sms_grant:sent", true);
//you may validate the user or set an error, doesn't matter anymore
//it will be overwritten
//...
我建议看看这个答案:
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);
}
这个问题是我上一个问题的延续:ASP.Net Identity 2 login using password from SMS - not using two-factor authentication
我构建了自定义 OAuthAuthorizationServerProvider 以支持自定义 grant_type。
我的想法是创建 sms
的 grant_type,这将允许用户生成一次性访问代码,该代码将发送到他的手机 phone,然后在使用 [= 发送请求时将用户作为密码42=] 的密码。
现在,在生成、存储并通过 SMS 发送密码后,我想 return 自定义响应,而不是来自我的 GrantCustomExtension 的令牌。
public override async Task GrantCustomExtension(OAuthGrantCustomExtensionContext context)
{
const string allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] {allowedOrigin});
if (context.GrantType != "sms")
{
context.SetError("invalid_grant", "unsupported grant_type");
return;
}
var userName = context.Parameters.Get("username");
if (userName == null)
{
context.SetError("invalid_grant", "username is required");
return;
}
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(userName);
if (user == null)
{
context.SetError("invalid_grant", "user not found");
return;
}
var generator = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
await userManager.UpdateSecurityStampAsync(user.Id);
var accessCode = await generator.GenerateAsync("SMS", userManager, user);
var accessCodeExpirationTime = TimeSpan.FromMinutes(5);
var result = await userManager.AddAccessCode(user, accessCode, accessCodeExpirationTime);
if(result.Succeeded)
{
Debug.WriteLine("Login code:"+accessCode);
//here I'll send login code to user phone via SMS
}
//return 200 (OK)
//with content type="application/json; charset=utf-8"
//and custom json content {"message":"code send","expires_in":300}
//skip part below
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "SMS");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
如何停止从 OAuthAuthorizationServerProvider 生成令牌和 return 自定义响应?
我知道两种方法:TokenEndpoint
、TokenEndpointResponse
,但我想覆盖整个响应,而不仅仅是令牌。
编辑:
现在,我正在使用以下代码在 GrantCustomExtension
中创建临时 ClaimsIdentity:
var ci = new ClaimsIdentity();
ci.AddClaim(new Claim("message","send"));
ci.AddClaim(new Claim("expires_in", accessCodeExpirationTime.TotalSeconds.ToString(CultureInfo.InvariantCulture)));
context.Validated(ci);
我正在覆盖 TokenEndpointResponse
:
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
if (context.TokenEndpointRequest.GrantType != "sms") return base.TokenEndpointResponse(context);
//clear response containing temporary token.
HttpContext.Current.Response.SuppressContent = true;
return Task.FromResult<object>(null);
}
这有两个问题:当我打电话给 context.Validated(ci);
时,我说这是一个有效的用户,但我想回复信息,我已经通过短信发送了访问代码。
HttpContext.Current.Response.SuppressContent = true;
清除响应,但我想 return 一些东西而不是空响应。
这更像是一种解决方法,而不是最终解决方案,但我相信这是解决问题的最可靠方法,无需重写默认 OAuthAuthorizationServerProvider
实现中的大量代码。
方法很简单:使用 Owin 中间件捕获令牌请求,如果发送了 SMS,则覆盖响应。
[评论后编辑] 修复了代码以允许根据此答案缓冲和更改响应主体
在您的 Startup.cs
文件中:
public void Configuration(IAppBuilder app)
{
var tokenPath = new PathString("/Token"); //the same path defined in OAuthOptions.TokenEndpointPath
app.Use(async (c, n) =>
{
//check if the request was for the token endpoint
if (c.Request.Path == tokenPath)
{
var buffer = new MemoryStream();
var body = c.Response.Body;
c.Response.Body = buffer; // we'll buffer the response, so we may change it if needed
await n.Invoke(); //invoke next middleware (auth)
//check if we sent a SMS
if (c.Get<bool>("sms_grant:sent"))
{
var json = JsonConvert.SerializeObject(
new
{
message = "code send",
expires_in = 300
});
var bytes = Encoding.UTF8.GetBytes(json);
buffer.SetLength(0); //change the buffer
buffer.Write(bytes, 0, bytes.Length);
//override the response headers
c.Response.StatusCode = 200;
c.Response.ContentType = "application/json";
c.Response.ContentLength = bytes.Length;
}
buffer.Position = 0; //reset position
await buffer.CopyToAsync(body); //copy to real response stream
c.Response.Body = body; //set again real stream to response body
}
else
{
await n.Invoke(); //normal behavior
}
});
//other owin middlewares in the pipeline
//ConfigureAuth(app);
//app.UseWebApi( .. );
}
在您的自定义授权方法中:
// ...
var result = await userManager.AddAccessCode(user, accessCode, accessCodeExpirationTime);
if(result.Succeeded)
{
Debug.WriteLine("Login code:"+accessCode);
//here I'll send login code to user phone via SMS
}
context.OwinContext.Set("sms_grant:sent", true);
//you may validate the user or set an error, doesn't matter anymore
//it will be overwritten
//...
我建议看看这个答案:
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);
}