【问题标题】:ASP.Net Identity 2 - custom response from OAuthAuthorizationServerProviderASP.Net Identity 2 - 来自 OAuthAuthorizationServerProvider 的自定义响应
【发布时间】:2016-04-15 12:39:56
【问题描述】:

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

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

现在通过 SMS 生成、存储和发送该密码后,我想返回自定义响应,而不是来自我的 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 中创建临时 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); 时,我说这是一个有效用户,但我想回复我已通过 SMS 发送访问代码的信息。 HttpContext.Current.Response.SuppressContent = true; 清除响应,但我想返回一些东西而不是空响应。

【问题讨论】:

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


    【解决方案1】:

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

    方法很简单:使用 Owin 中间件捕获令牌请求,如果发送了 SMS,则覆盖响应。

    [在 cmets 之后编辑] 修复了代码以允许根据此答案 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
    //...
    

    【讨论】:

    • 感谢您的解决方案。我马上试试。可悲的是OAuthAuthorizationServerProvider 没有为此提供任何简单的解决方案。我想我不是唯一一个想做这种混合身份验证的人。
    • 我试过了,不幸的是它不起作用。我已经设置了断点,我可以确认调试器访问了c.Get&lt;bool&gt;("sms_grant:sent") 中的代码,但响应没有改变——我仍然得到与以前相同的响应。你能试试你的代码吗?也许它只需要小修复
    • 我搜索了一下,并通过@Lazy Coder stackoverflow.com/a/36414238/965722 找到了这个答案,他的解决方案需要使用 MemoryStream 清除响应正文。我已经构建了 POC 解决方案。我会把它贴在我的问题下面,请看一下。也许有什么值得修复的。也请修正你的答案。我想奖励您的回答,因为它为我指明了正确的方向,但同时我希望您的回答是完整的工作解决方案。
    • 伟大的发现!我很高兴能帮到你。我已经编辑了我的答案并按照 Lazy Coder 的答案对其进行了修复。
    • 再次感谢您的帮助并为我指明正确的方向!也许有更简单的方法可以做到这一点,但现在它工作得很好。
    【解决方案2】:

    我建议看看这个答案:

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

    【讨论】:

      猜你喜欢
      • 2016-09-23
      • 1970-01-01
      • 2013-11-28
      • 2016-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-12
      相关资源
      最近更新 更多