ASP.Net Identity 2 - 来自OAuthAuthorizationServerProvider的自定义响应

时间:2016-04-15 12:39:57

标签: c# asp.net asp.net-mvc-4 asp.net-web-api asp.net-identity-2

这个问题是我之前的问题的继续:ASP.Net Identity 2 login using password from SMS - not using two-factor authentication

我已经构建了自定义OAuthAuthorizationServerProvider以支持自定义grant_type 我的想法是创建一个sms的grant_type,它允许用户生成一次性访问代码,该代码将发送到他的手机,然后在用grant_type密码发送请求时将用户作为密码。

现在生成,存储并通过短信发送密码后,我想返回自定义响应,而不是来自我的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返回自定义响应?

我知道两种方法:TokenEndpointTokenEndpointResponse,但我想覆盖整个响应,而不仅仅是令牌。

修改
目前我正在GrantCustomExtension使用以下代码创建临时ClaimIdentity:

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;清除响应,但我想返回一些内容而不是空响应。

2 个答案:

答案 0 :(得分:4)

这是一种解决方法,而不是最终解决方案,但我相信这是解决问题的最可靠方法,无需重写默认OAuthAuthorizationServerProvider实施中的大量代码。

方法很简单:使用Owin中间件来捕获令牌请求,并在发送短信时覆盖响应。

[评论后修改] 修复了代码,以便根据此回答缓存和更改回复正文https://stackoverflow.com/a/36414238/965722

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
//...

答案 1 :(得分:1)

我建议看看这个答案:

https://stackoverflow.com/a/24090287/2508268

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);
}