4

這個問題是我以前的一個延續:ASP.Net Identity 2 login using password from SMS - not using two-factor authenticationASP.Net身份2 - 自定義響應從OAuthAuthorizationServerProvider

我已經建立我的自定義OAuthAuthorizationServerProvider支持自定義grant_type。
我的想法是創建grant_type爲sms,它允許用戶生成一次性訪問代碼,該代碼將在發送帶有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返回自定義響應?

我知道兩種方法: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;清除響應,但我想返回一些內容而不是空的響應。

回答

4

這是更好的解決方法,然後是最終的解決方案,但我相信這是解決問題時最可靠的方法,無需重寫默認的OAuthAuthorizationServerProvider實現中的大量代碼。

該方法很簡單:使用Owin中間件來捕獲令牌請求,並在發送SMS時覆蓋響應。

[評論後編輯]修復了代碼,允許響應主體進行緩衝,並改變了按這個答案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 
//... 
+0

感謝您的解決方案。我會馬上嘗試。可悲的是'OAuthAuthorizationServerProvider'沒有提供任何簡單的解決方案。我想我不是唯一一個想要做這種混合身份驗證的人。 – Misiu

+0

我試過了,不幸的是它不工作。我已經設置了斷點,我可以確認調試器在'c.Get (「sms_grant:sent」)內部訪問過代碼'但響應沒有變化 - 我仍然得到和以前相同的響應。你可以試試你的代碼嗎?也許它只是需要小修補 – Misiu

+0

我已經搜索了一下,並通過@Lazy Coder發現了這個答案http://stackoverflow.com/a/36414238/965722他的解決方案需要使用MemoryStream清除響應主體。我已經構建了POC解決方案。我會在下面的問題中發佈它,請看看它。也許有一些值得修復的東西。也請修復你的答案。我想授予您的答案,因爲它指向我的正確軌道,但同時我希望您的答案是完整的工作解決方案。 – Misiu