管理认证令牌的最佳实践

31

我正在使用HttpCLient在Java中编写REST客户端,我所访问的REST API需要每个REST操作的身份验证令牌。该令牌有效期为24小时。

现在,我处理方式是每次需要进行REST调用时调用“getAuth()”方法,这似乎对身份验证服务器造成了额外的负担。

如何方便地存储此身份验证令牌并管理其生命周期?是否有任何文档记录的最佳实践?

我考虑了以下解决方案:

public class MySession {
    String user;
    String pass;
    public MySession(String user, String pass) {
        this.user = user;
        this.pass = pass;
    }

    public getAuth() {
        //user user, pass to get auth token 
    }
}

然后将sessions对象传递给任何需要令牌的类。如果令牌已过期,只需再次调用此方法即可。


你的客户端是什么样的?是Java应用程序吗?您是否担心在客户端或服务器端存储令牌? - cassiomolin
这是一个Java Dropwizard应用程序,我担心将令牌存储在数据库中并进行太多的数据库调用,相反,我是否应该继续使用令牌直到它过期,然后请求一个新的(当它抛出“令牌过期”异常或类似情况)... - user_mda
请问您能展示一下客户端代码吗?在客户端,如果将令牌存储在数据库中会导致性能问题,您可以使用内存缓存。如果您知道令牌将过期并且可以刷新它,那就刷新吧 :) - cassiomolin
我删除了我的答案,因为我误解了你的问题。我认为这取决于您正在使用的REST API的安全性考虑。您对所有请求使用相同的凭据吗?如果令牌有效期为24小时,则可以重复使用它直到过期。 - gabrielgiussi
@gabrielgiussi 是的,我对所有请求使用相同的凭据,因此该令牌在发行后的24小时内有效,我没有一个API来刷新它,而是在过期后必须请求一个新的令牌。我如何跟踪到期时间?我只能依赖异常来判断令牌是否已过期吗? - user_mda
10个回答

13

为了简洁起见,我假设您正在调用一个无法更改的端点。您的实现方式将严重取决于令牌是应用程序还是用户基础的(一个令牌用于共享应用程序实例上的所有用户,或一个令牌用于每个用户)。

如果是整个应用的一个授权令牌:

  • 将其存储在内存中,并附带时间戳作为生存时间(或者在令牌过期错误时捕获、请求新的令牌并重试原始请求),如果不存在/已过期则刷新它
  • 如果您担心在应用程序重新启动后重新请求API令牌,也可以将其存储在数据库中,如果存在,则在启动时加载它

如果是每个用户的一个令牌:

  • 将其存储在用户会话中,这正是会话所用的东西,如果您正在对用户进行身份验证,则他们将拥有会话,并且开销已经存在
  • 如果您不想在每次登录时重新请求令牌,请在数据库中存储其当前令牌,并在用户登录时将其加载到其会话中

嗨,戴夫,你的回答非常恰当。谢谢。这是整个应用程序的一个身份验证令牌。我的原始方法是继续使用令牌,然后捕获请求新令牌的异常。这样做,我会为每个REST调用观察异常吗?这是一个好习惯吗?如果我要观察每个REST调用,那么生存时间属性有什么区别?还有,我是在应用程序启动时请求令牌(第一个)吗? - user_mda
1
抱歉回复晚了 - 如果您捕获异常并在获取新令牌后重新发送请求,则不需要使用生存时间。无论如何,您都应该监视每个调用以查找异常,处理方面基本上没有额外开销,并且您希望确保没有其他问题(当您进行网络调用时,任何事情都可能出错)。我会在第一次请求时请求令牌 - 没有必要提前触发它。 - Dave L

4
我假设您正在使用OAuth进行授权。无论您使用JWT还是其他令牌,都与此情况无关。
在执行授权时,您将获得一个带有过期时间的访问令牌(access_token),并根据您请求的授权类型(客户端凭证、授权码、隐式、资源所有者)获得一个刷新令牌(refresh_token)。
客户端应保留access_token和其过期时间。如果发放了refresh_token,则必须保密(请注意为您的用例选择正确的授权方式)。
在后续调用中,客户端不应在每次调用时请求新的令牌,而应使用存储的access_token。
一旦API开始返回401未经授权,access_token可能已过期。如果您有refresh_token,则应尝试使用它来刷新access_token。
如果您没有refresh_token或刷新请求也失败,则可以执行新的授权流程。
您可以使用过期时间作为线索,以知道何时获取新的access_token,无论是通过刷新还是通过新的完整授权流程。这将避免401未经授权。无论如何,在使用有效的access_token进行某些调用后收到此响应时,您的客户端应具有回退策略。

我没有刷新令牌,因此我应该注意这个异常并重新设置身份验证令牌吗? - user_mda
当您再次收到“401未经授权”时,请使用凭据执行新的授权。如果您想进行一些优化,还可以使用过期时间提前生成新的令牌,即使它尚未过期。 - Daniel Cerecedo
谢谢,那么我将在应用程序启动时实例化一个带有令牌和TTL的“session”对象,并将其传递给任何进行REST调用的内容。当我开始收到401时,我会在此对象上设置新的令牌。正确吗? - user_mda
没错,那样做就可以了。 - Daniel Cerecedo

3
如果您担心对数据库的访问次数过多,那么我认为有很多网络活动。在您的情况下,我不建议使用Session,而是将令牌存储在客户端的cookie中。
在高流量环境中(我想您的环境是如此),使用Session可能会消耗大量服务器内存,并且可扩展性也可能成为一个问题,需要在集群中同步会话。
正如@Cássio Mazzochi Molin所提到的,您可以使用内存缓存来存储任何用户特定的数据和令牌。这将减少对数据库的访问次数,并在需要时更容易地扩展应用程序。

3
我建议你使用以下方案:
1)首先,调用auth(username, password) REST API获取身份验证令牌。如果给定的凭据正确,则只需将身份验证cookie与HTTP 200响应代码一起发送回客户端。
2)然后,您可以调用受保护的REST API。每次请求时都需要发送身份验证cookie。
3)Servlet过滤器(或类似物)检查每个传入请求并验证令牌。如果令牌有效,则请求转发到REST方法,否则您需要生成http 401/403响应。
我建议您不要编写自己的身份验证层。而是安装和使用现有的身份验证系统。我建议你使用OpenAM。它是一个出色的开源访问管理系统。
我还建议您不要在服务器端打开会话以进行身份验证。如果您有10个客户端,则需要服务器管理10个会话。这不是一个大问题。但是,如果您有100或1000或数百万个不同的客户端,则需要更多的内存来存储服务器上的会话。

3

您可以创建一个管理器并在登录时将身份验证Cookie存储在线程本地中,如下所示的代码。只要线程存在,您就可以从getAuth()获取Cookie。

public class Manager {
    private static final ThreadLocal<String> SECURITY_CONTEXT = new ThreadLocal<>();

    public static void setAuth(String auth) {
        SECURITY_CONTEXT.set(auth);
    }

    public static String getAuth() {
        return SECURITY_CONTEXT.get();
    }

    public static void clear(){
        SECURITY_CONTEXT.remove();
    }
}

2
实际标准不是实现自己的解决方案(安全的基本规则:不要实现自己的东西!),而是使用实际标准解决方案,即JSON Web Tokens
网站上有文档,但基本思想是,您只需要存储一个值(服务器的私钥),然后就可以验证最初由服务器发布的每个声明(在您的情况下将包含过期时间)。

2
你应该使用JsonWebToken (JWT)来处理这种事情。JWT内置了支持设置到期日期的功能。有很多库可以使用这个方法,你可以在这里阅读更多内容
目前有4个Java实现,它们都可以检查令牌是否仍然有效(exp检查)。enter image description here

2
所以,如果我理解正确,您正在为所有请求使用相同的令牌(这意味着只要您的应用程序正在运行并且您正在刷新令牌,您就应该没问题)。我曾经遇到过同样的问题,这是我解决它的方法。我有一个单例类,在应用程序启动时初始化一次,并在其失效时刷新令牌。我正在使用C#,Asp.NET MVC5和AutoFac进行DI,但我相信您可以使用Java和Spring做同样的事情。
链接:更新具有线程安全性的单例属性

0
使用 JSON Web Tokens 在两个客户端之间交换信息。令牌只在 24 小时内有效,此后头部的所有后续调用都将被拒绝。

-1
  1. 每个请求的身份验证令牌是正确的方法,考虑身份验证服务器的扩展性以解决性能问题。
  2. 在第一次成功的身份验证(用户名和密码)后,生成私钥公钥对。将私钥存储为会话安全令牌(SST)并将公钥作为公共安全客户端密钥(PSCK)发送给客户端。
  3. 除登录(或身份验证)以外的所有请求中,客户端将发送PSCK以防止用户名和密码被盗窃,服务器可以定期在内部验证PSCK以节省处理时间。
  4. 如果系统在身份验证方面存在性能问题,请设置可扩展的单独身份验证服务器。
  5. 不要缓存任何令牌或密码,不要交换未加密的信息并且不要通过URL参数发送。不要发布。

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