OAuth与自定义JWT认证

7
我正在努力使用OAuth和JWT实现自定义身份验证流程。
基本上,它应该按照以下步骤进行:
用户点击登录
用户被重定向到第三方OAuth登录页面
用户登录页面
我获取access_token并请求用户信息
我获取用户信息并创建自己的JWT令牌,以便来回发送
我一直在遵循这篇很棒的教程来构建OAuth身份验证:这里,唯一不同的部分是Jerrie使用的是Cookies。
到目前为止,我已经完成了以下工作:
已配置AuthenticationService。
services.AddAuthentication(options => 
{
    options.DefaultChallengeScheme = "3rdPartyOAuth";
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie() // Added only because of the DefaultSignInScheme
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = // Ommited for brevity
})
.AddOAuth("3rdPartyOAuth", options =>
{
    options.ClientId = securityConfig.ClientId;
    options.ClientSecret = securityConfig.ClientSecret;
    options.CallbackPath = new PathString("/auth/oauthCallback");

    options.AuthorizationEndpoint = securityConfig.AuthorizationEndpoint;
    options.TokenEndpoint = securityConfig.TokenEndpoint;
    options.UserInformationEndpoint = securityConfig.UserInfoEndpoint;

    // Only this for testing for now
    options.ClaimActions.MapJsonKey("sub", "sub");

    options.Events = new OAuthEvents
    {
        OnCreatingTicket = async context =>
        {
            // Request for user information
            var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

            var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
            response.EnsureSuccessStatusCode();

            var user = JObject.Parse(await response.Content.ReadAsStringAsync());

            context.RunClaimActions(user);
        }
    };
});

认证控制器

    [AllowAnonymous]
    [HttpGet("login")]
    public IActionResult LoginIam(string returnUrl = "/auth/loginCallback")
    {
        return Challenge(new AuthenticationProperties() {RedirectUri = returnUrl});
    }

    [AllowAnonymous]
    [DisableRequestSizeLimit]
    [HttpGet("loginCallback")]
    public IActionResult IamCallback()
    {
        // Here is where I expect to get the user info, create my JWT and send it back to the client
        return Ok();
    }

免责声明:此OAuth流程正在被纳入。我已经有一个用于创建和使用我的JWT的流程,一切正常。我不会在这里发布它,因为我的问题在那之前。

我想要的

在Jerrie的帖子中,您可以看到他设置了DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;。通过这种方式,当到达/auth/loginCallback时,我在HttpContext中拥有用户声明。 问题是我的DefaultAuthenticateScheme设置为JwtBearersDefault,当调用loginCallback时,我无法在Request中找到用户声明。 在这种情况下,如何访问在OnCreatingTicketEvent中获得的信息以在我的回调中使用?

奖励问题:我对OAuth并不了解(确信现在清楚了)。您可能已经注意到,我的options.CallbackPathChallenge中传递的RedirectUri不同。 我期望第三方OAuth提供商将调用option.CallbackPath,但事实并非如此(明显)。为使其工作,我确实必须将CallbackPath设置为我在OAuth提供程序配置中设置的相同值(例如,类似于Jerries使用GitHub的教程)。这是正确的吗?回调仅用于匹配配置而不做其他用途吗?我甚至可以注释掉CallbackPath指向的端点,它仍然以相同的方式工作...

谢谢!

2个回答

1

认证

正如Jerrie在他的帖子中所链接的那样,有一个关于认证中间件的很好的解释: https://digitalmccullough.com/posts/aspnetcore-auth-system-demystified.html

您可以在“身份验证和授权流程”部分看到一个流程图。

第二步是身份验证中间件调用默认处理程序的Authenticate方法

由于默认的身份验证处理程序是Jwt,因此在oauth流程之后,上下文没有填充用户数据, 因为它使用了CookieAuthenticationDefaults.AuthenticationScheme

请尝试:

[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
    //
    // Read external identity from the temporary cookie
    //
    var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    
    if (result?.Succeeded != true)
    {
        throw new Exception("Nein");
    }

    var oauthUser = result.Principal;

    ...

    return Ok();
}

重要方案概述:ASP.NET Core 2身份验证方案

您可以使用https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhttpcontextextensions.signinasync?view=aspnetcore-2.2持久化用户。

额外奖励

我必须将CallbackPath设置为与我在OAuth提供程序配置中设置的值相同(如Jerries教程与GitHub),才能使其工作。是这样吗?"

是的。 出于安全原因,注册的回调URI(在授权服务器上)和提供的回调URI(由客户端发送)必须匹配。 因此,您不能随意更改它,或者如果更改它,则必须在auth服务器上进行更改。

如果没有此限制,例如使用邮件格式错误的链接(带有修改后的回调URL)可能会获得授权。这被称为开放式重定向,RFC也提到了它:https://www.rfc-editor.org/rfc/rfc6749#section-10.15 OWASP有一个很好的描述:https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md "我甚至可以注释掉Endpoint CallbackPath,它仍然可以正常工作..."
这是因为客户端是受信任的(您提供了您的密钥,并且您不是完全前端单页面应用程序)。因此,您可以选择是否发送回调URI。但是,如果您发送它,它必须与服务器上注册的回调URI匹配。如果您不发送它,则认证服务器将重定向到在其端注册的URL。

https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1

重定向URI。可选项。如第3.1.2节所述。

https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2

授权服务器将用户代理重定向到客户端在客户端注册过程中或进行授权请求时先前与授权服务器建立的重定向终点。

https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.2

授权服务器必须要求以下客户端注册其重定向端点:
  • 公共客户端。
  • 使用隐式授权类型的机密客户端。

您的客户端是机密的,使用授权码授权类型(https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)。

https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.3

如果已注册多个重定向URI,仅注册了部分重定向URI或未注册任何重定向URI,则客户端必须在授权请求中使用“redirect_uri”请求参数包括重定向URI。您已注册您的重定向URI,因此授权服务器不会引发错误。

谢谢@LeBoucher,这很奏效 :) 我会尽快阅读你的所有链接,并且会在赏金到期之前给出正确的答案。 - João Menighin

0

[AllowAnonymous]

更改为 [Authorize]

'loginCallback' 端点上 (AuthController.IamCallback 方法)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接