【问题标题】:Issuing Tokens with AspNet.Security.OpenIdConnect.Server (RC2) - Invalid Client?使用 AspNet.Security.OpenIdConnect.Server (RC2) 颁发令牌 - 客户端无效?
【发布时间】:2016-10-09 07:58:48
【问题描述】:

我能够在 RC1 中提取它。 OpenIdConnectServerProvider 发生了很大变化。

我对资源所有者流程感兴趣,所以我的 AuthorizationProvider 如下所示:

    public sealed class AuthorizationProvider : OpenIdConnectServerProvider
    {

        public override Task MatchEndpoint(MatchEndpointContext context)
        {
            if (context.Options.AuthorizationEndpointPath.HasValue &&
                context.Request.Path.StartsWithSegments(context.Options.AuthorizationEndpointPath))
            {
                context.MatchesAuthorizationEndpoint();
            }

            return Task.FromResult<object>(null);
        }

        public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context)
        {
            context.Validate();
            await Task.FromResult<object>(null);
        }

        public override async Task ValidateTokenRequest(ValidateTokenRequestContext context)
        {                
            if (!context.Request.IsAuthorizationCodeGrantType() &&
                !context.Request.IsRefreshTokenGrantType() &&
                !context.Request.IsPasswordGrantType())
            {
                context.Reject(
                    error: "unsupported_grant_type",
                    description: "Only authorization code, refresh token, and ROPC grant types " +
                                 "are accepted by this authorization server");
            }

            /* This is where the problem is. This context.Validate()
               will automatically return a 400, server_error, with
               message "An internal server error occurred."

               If I commented this out, I will get a 400, invalid_client.

               If I put in an arbitrary client like "any_client", it
               goes to GrantResourceOwnerCredentials, as I expect.
               However, I get a 500 with no explanation when it executes.
               See the function below for more details.
            */
            context.Validate();
            await Task.FromResult<object>(null);
        }

        public override Task HandleUserinfoRequest(HandleUserinfoRequestContext context)
        {                
            context.SkipToNextMiddleware();

            return Task.FromResult<object>(null);
        }

        public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
        {
             MYDbContext db = context.HttpContext.RequestServices.GetRequiredService<MyDbContext>();
             UserManager<MyUser> UM = context.HttpContext.RequestServices.GetRequiredService<UserManager<MyUser>>();

             MyUser user = await UM.FindByNameAsync(context.Request.Username);

             if (user == null)
             {
                context.Reject(
                   error: "user_not_found",
                   description: "User not found");

                return;
             }

             bool passwordsMatch = await UM.CheckPasswordAsync(user, context.Request.Password);

             if (!passwordsMatch)
             {
                 context.Reject(
                    error: "invalid_credentials",
                    description: "Password is incorrect");

                 return;
             }

             var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);

            identity.AddClaim(ClaimTypes.Name, user.UserName, "id_token token");

            /* I set the breakpoint on this line, and the execution
               does not hit this breakpoint. I immediately get a 500.
               My output says 'System.ArgumentException' in
               AspNet.Security.OpenIdConnect.Extensions.dll
            */
            List<string> roles = (await UM.GetRolesAsync(user)).ToList();

            roles.ForEach(role =>
            {
                identity.AddClaim(ClaimTypes.Role, role, "id_token token");
            });

            var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity),
                                                  new AuthenticationProperties(),
                                                  context.Options.AuthenticationScheme);

            ticket.SetResources(new[] { "mlm_resource_server" });
            ticket.SetAudiences(new[] { "mlm_resource_server" });
            ticket.SetScopes(new[] { "defaultscope" });

            context.Validate(ticket);
        }
    }

顺便说一句,我正在尝试在 Fiddler 上运行它:

POST /token HTTP/1.1
Host: localhost:56785
Content-Type: application/x-www-form-urlencoded

username=user&password=pw&grant_type=password

当密码不正确时,我得到预期的 400 拒绝,但当密码正确时,我得到 500。

我错过了什么?我现在建立该用户身份的方式不正确吗?我应该重写另一个函数吗?

注意 - 我没有提供我的启动文件,因为我认为它无关紧要。如果绝对需要,我稍后会发布。

【问题讨论】:

    标签: asp.net-core openid-connect aspnet-contrib


    【解决方案1】:

    如果您启用了日志记录,您会立即明白发生了什么:当请求中缺少 client_id 时,OpenID Connect 服务器中间件不允许您将令牌请求标记为“完全验证”:

    if (context.IsValidated && string.IsNullOrEmpty(request.ClientId)) {
        Logger.LogError("The token request was validated but the client_id was not set.");
    
        return await SendTokenResponseAsync(request, new OpenIdConnectMessage {
            Error = OpenIdConnectConstants.Errors.ServerError,
            ErrorDescription = "An internal server error occurred."
        });
    }
    

    如果您想让客户端身份验证可选,请改为调用context.Skip()


    请注意,您的提供商有几个问题:

    ValidateAuthorizationRequest 不验证任何内容,这可怕因为任何redirect_uri 都将被视为有效(= 一个巨大的开放重定向缺陷)。幸运的是,由于您只对 ROPC 授权感兴趣,因此您可能不会实现任何交互式流程。我建议删除此方法(您也可以删除 MatchEndpoint)。


    您在ValidateTokenRequest 中的初始授权检查有问题,因为您在调用context.Reject() 后没有停止代码的执行,最终导致context.Validate() 被调用。


    identity.AddClaim(ClaimTypes.Name, user.UserName, "id_token token") 不再是有效的语法。 ArgumentException 很可能是由这个检查引起的:

    if (destinations.Any(destination => destination.Contains(" "))) {
        throw new ArgumentException("Destinations cannot contain spaces.", nameof(destinations));
    }
    

    改用这个:

    identity.AddClaim(ClaimTypes.Name, user.UserName,
        OpenIdConnectConstants.Destinations.AccessToken,
        OpenIdConnectConstants.Destinations.IdentityToken);
    

    如果您仍然不确定您的提供商应该是什么样子,请不要犹豫,看看这些具体示例:

    【讨论】:

    • 非常感谢您提供这些信息。我现在得到一个访问令牌。现在,我的资源服务器在一个单独的项目中。当我使用 [Authorize] 装饰控制器或其功能时,如果我不包含 Authorization: Bearer 标头,它将让我通过。您能否也指出如何使用此处发行的令牌?我认为我已经从 github 和其他人的示例中遵循了我认为正确的所有内容。
    • 令牌验证部分没有改变,并且不是由 OIDC 服务器中间件管理,而是由 JWT 中间件(如果您强制 OIDC 服务器颁发 JWT 令牌)、新的 OAuth2 验证中间件(对于加密令牌,新的默认格式)或 OAuth2 自省中间件。也许您应该发布一个新问题并分享一些代码来确定为什么[Authorize] 不能按预期工作。
    • 我从头开始了资源服务器项目(空项目)。在构建控制器和启动文件时,我确保只安装 rc2 包。我一定有不正确的包,虽然这可能不是真正的原因,但尽管如此,现在一切正常。谢谢。
    • 太棒了!如果您没有其他问题,请随时将此答案标记为已接受;)
    猜你喜欢
    • 2016-01-28
    • 2016-03-31
    • 1970-01-01
    • 2019-05-24
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 2018-09-21
    相关资源
    最近更新 更多