当redirect_uri包含查询字符串参数时,DotNetOpenAuth拒绝访问令牌请求

时间:2016-05-19 16:56:31

标签: c# oauth-2.0 dotnetopenauth owin-middleware

我正在使用OWIN中间件开发OAuth v2授权服务器,我正在尝试实现OAuth2 Authorization Code Grant Flow但是我遇到了一个问题,如果redirect_uri包含查询字符串参数,我的客户端会收到错误资源所有者必须进行身份验证。

客户端可以请求验证码,但是当它尝试交换访问令牌的代码时,授权服务器会响应HTTP状态“400 Bad Request”和以下响应正文。

{"error":"invalid_grant"}

如果redirect_uri不包含查询字符串参数,或者如果客户端在请求授权代码之前将其删除,那么它可以正常工作。如果资源所有者已经使用授权服务器进行了身份验证,那么它也可以正常运行。

我的客户端正在使用DotNetOpenAuth并使用Glimpse我可以看到redirect_uri在授权代码请求和访问令牌请求之间是一致的。

Glimpse日志中的失败如下所示:

Prepared outgoing EndUserAuthorizationRequestC (2.0) message for http://localhost:61814/authorize:
    client_id: localhost36618
    redirect_uri: http://localhost:36618/login?redirectURL=%2FProfile
    state: <state token>
    scope: authentication
    response_type: code

Processing incoming EndUserAuthorizationSuccessAuthCodeResponse (2.0) message:
    code: <authorization code>
    state: <state token>
    redirectURL: /Profile

Prepared outgoing AccessTokenAuthorizationCodeRequestC (2.0) message for http://localhost:61814/token:
    code: <authorization code>
    redirect_uri: http://localhost:36618/login?redirectURL=%2FProfile
    grant_type: authorization_code

http://localhost:61814/token returned 400 BadRequest: Bad Request

WebException from http://localhost:61814/token: {"error":"invalid_grant"}

但是,如果我从redirect_uri中省略了查询字符串参数,那么它可以工作:

Prepared outgoing EndUserAuthorizationRequestC (2.0) message for http://localhost:61814/authorize:
    client_id: localhost36618
    redirect_uri: http://localhost:36618/Login
    state: <state token>
    scope: authentication
    response_type: code

Processing incoming EndUserAuthorizationSuccessAuthCodeResponse (2.0) message:
    code: <authorization code>
    state: <state token>

Prepared outgoing AccessTokenAuthorizationCodeRequestC (2.0) message for http://localhost:61814/token:
    code: <authorization code>
    redirect_uri: http://localhost:36618/Login
    grant_type: authorization_code

Processing incoming AccessTokenSuccessResponse (2.0) message:
    access_token: <access token>
    token_type: bearer
    expires_in: 3599
    refresh_token: <refresh token>

同样,如果我在使用客户端之前登录授权服务器,它可以工作:

Prepared outgoing EndUserAuthorizationRequestC (2.0) message for http://localhost:61814/authorize:
    client_id: localhost36618
    redirect_uri: http://localhost:36618/login?redirectURL=%2FProfile
    state: <state token>
    scope: authentication
    response_type: code

Processing incoming EndUserAuthorizationSuccessAuthCodeResponse (2.0) message:
    code: <authorization code>
    state: <state token>
    redirectURL: /Profile

Prepared outgoing AccessTokenAuthorizationCodeRequestC (2.0) message for http://localhost:61814/token:
    code: <authorization code>
    redirect_uri: http://localhost:36618/login?redirectURL=%2FProfile
    grant_type: authorization_code

Processing incoming AccessTokenSuccessResponse (2.0) message:
    access_token: <access token>
    token_type: bearer
    expires_in: 3599
    refresh_token: <refresh token>    

OWIN授权服务器的OAuthAuthorizationServerProvider在成功的访问令牌授权代码请求上执行以下方法:

  • Provider.OnMatchEndpoint
  • Provider.OnValidateClientAuthentication
  • AuthorizationCodeProvider.Receive
  • Provider.OnValidateTokenRequest
  • Provider.OnGrantAuthorizationCode
  • Provider.OnTokenEndpoint
  • AccessTokenProvider.OnCreate
  • RefreshTokenProvider.OnCreate
  • Provider.OnTokenEndpointResponse

然而,不成功的访问令牌授权代码请求仅涉及以下方法:

  • Provider.OnMatchEndpoint
  • Provider.OnValidateClientAuthentication
  • AuthorizationCodeProvider.Receive

AuthorizationCodeProvider.OnReceive具有以下实现:

private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
{
 try
 {
  string ticket = _repo.RemoveTicket(context.Token);
  if (!string.IsNullOrEmpty(ticket))
  {
   context.DeserializeTicket(ticket);
  }
 }
 catch (Exception ex)
 {
  var wrapper = new Exception("Receive Authentication Code Error", ex);
  Logger.Error(wrapper);
 }
}

在调试器中,我可以看到有效的令牌,从_repo成功检索序列化的Ticket,以及在方法完成之前的上下文对象中的反序列化的Ticket。没有异常被抛出。成功和失败的请求之间的流程看起来相同,所以我不清楚什么是失败的,诊断工具事件日志在请求处理期间没有显示任何异常,只是在ReceiveAuthenticationCode完成后退出的线程。

看起来问题出在我的测试客户端上,因为我能够使用Brent Shaffer's OAuth2 Demo PHP live demo重现完全相同的问题。

测试客户端基于DotNetOpenAuth:

private static class Client
{
    public const string Id = "username";
    public const string Secret = "password";
}
private static class Paths
{
    public const string AuthorizationServerBaseAddress = "http://localhost:61814";
    public const string ResourceServerBaseAddress = "http://localhost:61814";
    public const string AuthorizePath = "/authorize";
    public const string TokenPath = "/token";
    public const string ResourceServerApiMethodPath = "/getaccount";
 }

public ActionResult Login(string code = "", string redirectURL = "/profile")
{
    var authorizationServerUri = new Uri(Paths.AuthorizationServerBaseAddress);
    var authorizationServer = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri(authorizationServerUri, Paths.AuthorizePath),
        TokenEndpoint = new Uri(authorizationServerUri, Paths.TokenPath)
    };
    var client = new WebServerClient(authorizationServer, Client.Id, Client.Secret);


    if (!string.IsNullOrEmpty(code))
    {
        var apiResponse = null;
        var authorizationState = client.ProcessUserAuthorization();

        if (!string.IsNullOrEmpty(authorizationState?.AccessToken))
            apiResponse = GetApiResponse(authorizationState, Paths.ResourceServerApiMethodPath);

        if (apiResponse != null)
        {
            var identity = new ClaimsIdentity(new[] { new Claim("test", apiResponse),
                                                      new Claim(ClaimTypes.Role, "ResourceOwner")
                                                }, "ApplicationCookie");

            AuthMgr.SignIn(new AuthenticationProperties { IsPersistent = true }, identity);

            return Redirect(redirectURL);
        }
    }
    else
    {
        client.RequestUserAuthorization();
    }

    return View();
}

知道DotNetOpenAuth我可能做错了什么吗?为什么我在服务器端看不到无效请求?

1 个答案:

答案 0 :(得分:0)

我解决了。 获取access_token不要使用webclient.ProcessUserAuthorization(Request)方法。

 using (var client = new HttpClient())
        {
            var uri = new Uri($"http://localhost:24728/ClientAuthorization?tourl={tourl}");

            var httpContent = new FormUrlEncodedContent(new Dictionary<string, string>()
                {
                    {"code", code},
                    {"redirect_uri", uri.AbsoluteUri},
                    {"grant_type","authorization_code"},
                    {"client_id", ClientStartupProfile.Client.ClientId},
                    {"client_secret", ClientStartupProfile.Client.Secret}
                });

            var response = await client.PostAsync(ClientStartupProfile.AuthorizationServer.TokenUri, httpContent);
            var authorizationState = await response.Content.ReadAsAsync<AuthorizationState>();

            //判断access_token 是否获取成功
            if (!string.IsNullOrWhiteSpace(authorizationState.AccessToken))
                Response.Cookies.Add(new System.Web.HttpCookie("access_token", authorizationState.AccessToken));

            return Redirect(tourl);
        }