处理OAuth响应和会话

huangapple go评论87阅读模式
英文:

Handling OAuth Responses & Sessions

问题

在OAuth2令牌交换的最后,我通常会得到一个用户数据的JSON数组,我将其解组为一个结构体(比如GoogleUser),其中包含我关心的字段。

将这些数据记录到我的数据库中,有什么明智的方法吗?我是否应该在回调处理程序中调用一个CreateUser函数,传递该结构体并保存它(对我来说是显而易见的方式),在检查用户在数据库中是否已经存在之后?

我假设我应该在回调处理程序中创建一个会话令牌(即session.Values["authenticated"] == true),将其存储在一个带有合理过期日期的cookie中,并且只需在任何期望已登录用户的处理程序函数中检查if authenticated == true。或者对于管理员处理程序:if admin_user == true在这里(如果有的话)存在哪些风险,假设我正在使用HTTPS和安全的cookie?

对于这些基本问题,我表示歉意:只是想了解使用OAuth登录用户的“最佳实践”方式。

英文:

At the end of an OAuth2 token exchange, I'm [typically] left with a JSON array of user data that I've un-marshalled into a struct (say, GoogleUser) with the fields I care about.

What is the sensible way of recording that data to my DB? Just call a CreateUser function from the callback handler, pass the struct and save it (the obvious way to me), after checking that the user doesn't already exist in the DB?

I assume I should then create a session token (i.e. session.Values["authenticated"] == true) in the callback handler, store that in a cookie (with a reasonable expiry date) and simply just check for if authenticated == true on any handler functions that expect a logged-in user? Or, for admin handlers: if admin_user == true. What are the risks here (if any) presuming I'm talking over HTTPS and using secure cookies?

Apologies for the basic questions: just trying to get a grip on "best practice" ways to log users in w/ OAuth.

答案1

得分: 1

OAuth标准中,Access Token具有过期时间。通常由授权服务器确定。在您的情况下,我假设您在授权服务器端。

阅读RFC 6750的示例:

>通常,作为OAuth 2.0 [RFC6749]访问令牌响应的一部分,将一个承载令牌返回给客户端。这样的响应示例如下:

 HTTP/1.1 200 OK
 Content-Type: application/json;charset=UTF-8
 Cache-Control: no-store
 Pragma: no-cache

 {
   "access_token":"mF_9.B5f-4.1JqM",
   "token_type":"Bearer",
   "expires_in":3600,
   "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
 }

还要阅读RFC 6749中的Access Token概念:

>访问令牌提供了一个抽象层,用单个令牌替换了不同的授权构造(例如用户名和密码),该令牌由资源服务器理解。这种抽象使得发放的访问令牌比用于获取它们的授权授予更加严格,并且消除了资源服务器理解各种身份验证方法的需要。

所以在您的情况下,我认为不需要“cookie”或“管理员处理程序”。您只需要像OAuth规范所说的那样为每个登录的用户生成Access TokenRefresh Token,并存储其过期时间。您还可以提供与Access Token相关的哈希方法,以确保它是一个合法的请求。例如,用户使用其访问令牌使用哈希和盐方法生成签名,将访问令牌和签名发送到服务器进行验证。阅读https://stackoverflow.com/questions/15985401/public-key-encryption/了解更多详细信息。

此外,您不需要将这些令牌保存到数据库中,因为它们都是临时资源。您还可以将所有用户信息保存在内存中,并实现一个缓存层,定期将这些真正重要的信息保存到数据库中(这是我目前正在使用的方法),以降低数据库压力。

英文:

The Access Token in OAuth standard have a expiry. It's usually determined by authorization server. In your case I assume you are on authorization server side.

Read RFC 6750 for example:

>Typically, a bearer token is returned to the client as part of anOAuth 2.0 [RFC6749] access token response. An example of such a response is:

 HTTP/1.1 200 OK
 Content-Type: application/json;charset=UTF-8
 Cache-Control: no-store
 Pragma: no-cache

 {
   "access_token":"mF_9.B5f-4.1JqM",
   "token_type":"Bearer",
   "expires_in":3600,
   "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
 }

Also read concept of Access Token in RFC 6749:

>The access token provides an abstraction layer, replacing different
authorization constructs (e.g., username and password) with a single
token understood by the resource server. This abstraction enables
issuing access tokens more restrictive than the authorization grant
used to obtain them, as well as removing the resource server's need
to understand a wide range of authentication methods.

So in your case, I don't think a "cookie" or "admin handler" is needed. You only have to generate Access Token & Refresh Token for each users logged in just like OAuth spec says, and store its expiry as well. You can also provide a hash method related with Access Token to make sure it's a legal request. For example, users use their access token to generate a signature with hash & salt method, send access token & signature to server to verify. Read https://stackoverflow.com/questions/15985401/public-key-encryption/ for more details.

Furthermore, you don't need to save these tokens into your DB because they are all temporary resources. You can also save all user informations in memory and implement a cache layer to save these informations which truly important into DB periodically(which I'm currently using now) to lower DB pressure.

答案2

得分: 1

关于你的第一个问题,通常建议在一个事务中进行检查和插入。这取决于你使用的数据库,但通常被称为UPSERT语句。在PLSQL中,它看起来有点像这样(根据需要进行修改):

CREATE FUNCTION upsert_user(emailv character varying, saltv character varying, hashv character varying, date_createdv timestamp without time zone) RETURNS void
    LANGUAGE plpgsql
AS $$;
BEGIN
    LOOP
        -- 首先尝试更新键
        UPDATE users SET (salt, hash) = (saltv, hashv) WHERE email = emailv;
        IF found THEN
            RETURN;
        END IF;
        -- 如果不存在,则尝试插入键
        -- 如果其他人同时插入相同的键,
        -- 我们可能会遇到唯一键冲突
        BEGIN
            INSERT INTO users(email, salt, hash, date_created) VALUES (emailv, saltv, hashv, date_createdv);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- 什么都不做,并循环尝试再次更新
        END;
    END LOOP;
END;
$$;

关于你的第二个问题,通常使用HTTPS上的Secure cookie就足够了。我会设置HttpOnly选项,通常也会设置Path选项。

HttpOnly意味着cookie不能被JS访问(只能通过HTTP或HTTPS),而Path选项允许你指定cookie在URL中有效的路径。

英文:

With regards to your first question, It's usually recommended to do the check and insert in a single transaction. It depends on what DB you're using, but these are usually referred to as UPSERT statements. In PLSQL it looks a bit like this (modify to taste):

CREATE FUNCTION upsert_user(emailv character varying, saltv character varying, hashv character varying, date_createdv timestamp without time zone) RETURNS void
    LANGUAGE plpgsql
AS $$;
BEGIN
    LOOP
        -- first try to update the key
        UPDATE users SET (salt, hash) = (saltv, hashv) WHERE email = emailv;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO users(email, salt, hash, date_created) VALUES (emailv, saltv, hashv, date_createdv);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$;

In regards to your second question, usually Secure cookies over HTTPS is enough. I'd set the HttpOnly option, and usually the Path option as well.

HttpOnly means that the cookie can't be accessed by JS (only HTTP or HTTPS), and the Path option allows you to specify what path (in the URL) the cookie is valid for.

huangapple
  • 本文由 发表于 2013年5月19日 22:19:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/16635644.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定