如何存储访问令牌?(Oauth 2,授权码流)

44

据我所知,授权码流的目的是用于交换认证码以获取访问令牌。此交换发生在为页面提供服务的服务器和授权服务器之间,以便实际的访问令牌不会暴露给客户端用户。

一旦获取到访问令牌,页面服务器应该如何存储它呢?我从Pluralsight的一个示例中了解到以下代码:

    public static HttpClient GetClient()
    {
        HttpClient client = new HttpClient();
        var accessToken = RequestAccessTokenAuthorizationCode();
        client.SetBearerToken(accessToken);

        client.BaseAddress = new Uri(IdentityConstants.API);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        return client;
    }

    private static string RequestAccessTokenAuthorizationCode()
    {
        // did we store the token before?
        var cookie = HttpContext.Current.Request.Cookies.Get("ClientMVCCookie.AuthCode");
        if (cookie != null && cookie["access_token"] != null && !string.IsNullOrEmpty(cookie["access_token"]))
        {
            return cookie["access_token"];
        }

        // no token found - request one

        // we'll pass through the URI we want to return to as state
        var state = HttpContext.Current.Request.Url.OriginalString;

        var authorizeRequest = new IdentityModel.Client.AuthorizeRequest(
            IdentityConstants.AuthEndoint);

        var url = authorizeRequest.CreateAuthorizeUrl(IdentityConstants.MVCClientSecret, "code", "management secret",
            IdentityConstants.MVCAuthCodeCallback, state);

        HttpContext.Current.Response.Redirect(url);

        return null;
    }
}

这将导致每个请求都检查cookie中是否存储了访问令牌。如果没有,则会启动流程。回调函数如下:

public class CallbackController : Controller
{
    // GET: STSCallback
    public async Task<ActionResult> Index()
    {
        // get the authorization code from the query string
        var authCode = Request.QueryString["code"];

        // with the auth code, we can request an access token.
        var client = new TokenClient(
            IdentityConstants.TokenEndoint,
            "mvc_client_auth_code",
             IdentityConstants.MVCClientSecretAuthCode);

        var tokenResponse = await client.RequestAuthorizationCodeAsync(
            authCode,
            IdentityConstants.MVCAuthCodeCallback);

        // we save the token in a cookie for use later on
        var cookie = Response.Cookies["ClientMVCCookie.AuthCode"];
        cookie.Expires = DateTime.Now.AddMinutes(1);
        cookie["access_token"] = tokenResponse.AccessToken;

        // get the state (uri to return to)
        var state = Request.QueryString["state"];

        // redirect to the URI saved in state
        return Redirect(state);
    }
}

把访问令牌存储在cookie中不是破坏授权码流程的整个目的吗?Cookie将被传输到客户端浏览器,从而暴露给客户端?我有遗漏什么吗?如果这不是存储令牌的正确方法,应该如何存储?

3个回答

66
在OAuth术语中,客户端是向资源服务器发出请求的组件,在您的情况下,客户端是Web应用程序的服务器(而不是浏览器)。因此,访问令牌只应存储在Web应用程序服务器上。它不应该暴露给浏览器,并且也不需要,因为浏览器从不直接向资源服务器发出任何请求。相反,浏览器与Web应用程序服务器通信,后者再使用访问令牌向资源服务器发出请求。浏览器如何向Web应用程序服务器进行身份验证与OAuth 2.0无关。例如,它可能是一个常规会话cookie,而Web应用程序服务器可能将每个会话或每个用户与访问令牌相关联。换取访问令牌的令牌请求由Web应用程序服务器执行,Web应用程序服务器应使用共享的client_secret对自己进行身份验证。授权代码流确保可以对客户端进行身份验证,从而防止恶意客户端冒充合法客户端。并非所有Web应用程序客户端都具有服务器组件,在某些情况下,直接由浏览器中的JavaScript代码向资源服务器发出请求。在这种情况下,浏览器是客户端,访问令牌必须由浏览器存储(在JavaScript变量、本地存储或Cookie中)。在这种情况下,客户端无法进行身份验证(但可以通过使用TLS和服务器仅重定向到注册的终端点URL来实现合理的安全性)。有关OAuth 2.0安全性的推荐阅读:https://www.rfc-editor.org/rfc/rfc6819#section-4.3.3(RFC 6819)。

4
我完全理解那部分。我想问的是问题中我包含的例子是否正确。这个例子在ASP.NET MVC服务器上使用了一个cookie。我认为这个包含令牌的 cookie 会传递给浏览器(对吗?),然后暴露出令牌。 - BodzioSamolot
1
这是一个mvc服务器端应用程序。回调控制器使用授权代码与授权服务器交换访问令牌。然后,将令牌包含在cookie中。这是我发现的一个例子。我知道令牌不能暴露给浏览器。这就是为什么保存在cookie中让我困惑的原因。这难道不意味着它会随着后续请求一起传送到浏览器吗? - BodzioSamolot
@JuanGaitán - 这就是我正在寻找答案的问题。 - OnNIX
1
@Mercury 如果您在前端请求和存储访问令牌,那么您正在创建一个公共客户端。这是不同的OAuth流程和常见做法,没有任何问题。如果您使用CORS+PKCE而不是隐式授权,这也与本地客户端一样安全。如果您有后端,也可以使用此方法,但我认为这主要取决于API请求的位置(前端或后端)。 - Florian Winter
2
@Mercury 关于cookies和sessions:1)Cookies只是前端到后端身份验证的示例。如果后端是OAuth客户端,则这超出了OAuth的范围,这是我的答案的主要观点。2)我不会为cookies或tokens辩论,而是指向这个链接http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/和这个反对意见:https://www.ducktypelabs.com/review-stop-using-jwt-for-sessions/。我们可能想到的大多数争论都已经在那里涵盖了。我的观点是两者都可以,各有优缺点。 - Florian Winter
显示剩余7条评论

5
这个 Cookie 不会暴露给浏览器。它是授权服务器返回给客户端(自身为服务器而非浏览器)的响应的一部分。实现重定向端点的 CallbackController 从响应中提取 Cookie。
该 Cookie 不会传递给浏览器。浏览器如何验证自己与客户端应用程序服务器的身份在您的示例代码中未显示,也不是 OAuth 的一部分。
授权服务器可以将令牌存储在请求正文(例如 JSON 格式)中,而不是在 Cookie 中。然而,这没有任何区别,因为客户端可以看到和处理整个响应。
详情请参见我的另一个答案:https://dev59.com/s1cP5IYBdhLWcg3wf58G#44655679 顺便说一下:这个 CallbackController 使用 state 来存储要重定向浏览器的最终 URL。这是非标准的,但可行。不过,state 实际上是用来防止重定向端点受到 CSRF 攻击的。这个 CallbackController 没有验证 state,而是盲目地重定向到给定的任何 URL。可能是由于代码是作为示例而编写的,所以这个细节被省略了。然而,这表明这个代码可能并不完全适用于生产环境。

谢谢 ;) 这正是我所缺少的。客户端不一定会将 cookie 传递给浏览器。 - BodzioSamolot

1
如果您想从浏览器请求一个rest资源,您需要使用“隐式授权”流程。查看此Auth0帖子以在https://auth0.com/docs/api-auth/which-oauth-flow-to-use之间进行选择。如果您想要使用服务器的访问令牌,您应该存储“授权码”并在每次需要时生成一个“访问令牌”,“访问令牌”不应该存活超过5分钟,您不需要存储它。

3
“授权码”的使用寿命比“访问令牌”大多数情况下要短。OAuth 2.0 规范建议其最长生命周期为10分钟,但实际上,大多数服务将其设置得更短,大约在30-60秒左右。因为授权码的用途是短暂且一次性的[...]”那么为什么不存储访问令牌和刷新令牌呢? - jona303
单页 Web 应用程序也可以使用具有 PKCE 的授权代码流。它只需要授权服务器启用 CORS,通常如果授权服务器也是资源服务器,则已经是必需的要求。请参见 https://www.youtube.com/watch?v=CHzERullHe8 和 https://tools.ietf.org/html/draft-ietf-oauth-browser-based-apps-00。 - Florian Winter
如@jona303所说,授权码只能使用一次。如果你想在服务器上保留用户的访问令牌,你需要保存并使用刷新令牌。对于服务器身份/令牌,只需使用“client_credentials”流来在到期前快速检索新的访问令牌即可。 - Charlie Reitzel

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