【问题标题】:How to create a authentication token using Java如何使用 Java 创建身份验证令牌
【发布时间】:2012-12-09 04:49:25
【问题描述】:

在我的 Java EE6 REST 服务上,我想使用身份验证令牌从移动设备登录,用户将发送他们的用户名、密码,服务器将发回一个令牌,该令牌将用于授权用户进一步请求给定时间。

我可以像这样简单地自己创建一个令牌吗?(我想我不需要加密,因为我将使用 HTTPS。)

String token = UUID.randomUUID().toString().toUpperCase() 
            + "|" + "userid" + "|"
            + cal.getTimeInMillis();

或者有更标准的方法来创建这些令牌?也许它存在于其中一个 API 中?

【问题讨论】:

  • 您当前的令牌结构具有高度欺骗性。如果有特殊原因您不想使用 Shiro 或 Seam Security 等经过验证的安全库?
  • @Perception tnx 我一个都不知道,它们是用来创建令牌的?
  • 每个人都试图以各种方式向您说明的一点是,编写自己的安全框架要么 1) 相当困难,要么 2) 不太安全。除非您真的不关心安全性,否则请使用经过验证的解决方案,在这种情况下,您为什么还要搞砸它?
  • @Ryan Stewart tnx 我更新了问题
  • @Perception 我更新了问题

标签: java security rest authentication token


【解决方案1】:

对于 Java 8 及更高版本,最快和最简单的解决方案是:

private static final SecureRandom secureRandom = new SecureRandom(); //threadsafe
private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder(); //threadsafe

public static String generateNewToken() {
    byte[] randomBytes = new byte[24];
    secureRandom.nextBytes(randomBytes);
    return base64Encoder.encodeToString(randomBytes);
}

输出示例:

wrYl_zl_8dLXaZul7GcfpqmDqr7jEnli
7or_zct_ETxJnOa4ddaEzftNXbuvNSB-
CkZss7TdsTVHRHfqBMq_HqQUxBGCTgWj
8loHzi27gJTO1xTqTd9SkJGYP8rYlNQn

以上代码将生成带有 32 个字符的 base64 编码的随机字符串。在 Base64 编码中,每个 char 编码 6 位数据。因此,对于上面示例中的 24 个字节,您将获得 32 个字符。您可以通过更改随机字节数来更改输出字符串的长度。此解决方案比UUID(仅使用 16 个随机字节)更安全,并生成可安全用于 HTTP url 的字符串。

【讨论】:

    【解决方案2】:

    要在 Java 中创建一个难以猜测的令牌,请使用 java.security.SecureRandom

    例如

    SecureRandom random = new SecureRandom();
    byte bytes[] = new byte[20];
    random.nextBytes(bytes);
    String token = bytes.toString();
    

    与其在令牌中包含用户名,不如在内存或数据库中缓存 user:token 映射。

    【讨论】:

    • 使用字节数组和数组上的toString 方法来生成令牌可能不是一个好的解决方案,正如here 所解释的那样。实际上,您可以找到更好的解决方案here
    • 您不应使用toString() 将字节标记转换为字符串。不可打印的字节将丢失,因此令牌长度将不一致。使用 Hex、Base64 或类似编码转换为可打印字符串。
    【解决方案3】:

    您提出的方案有效地允许客户无限制地访问您的服务。初始登录后,UID 和“用户 ID”将提供给客户端,它们可以简单地与始终有效的时间戳相结合。

    如果您需要具有“登录”和会话令牌的服务,那么为什么不直接使用 HttpSession?

    【讨论】:

    • @Spring 您实际上并没有说明您使用什么来发布您的 REST 服务(我假设是 jax-rs),所以很难给出答案。也许问/研究另一个问题?
    • 我创建了另一个问题,stackoverflow.com/questions/13997040/…
    【解决方案4】:

    有一种方法可以创建不会被破坏但也可以用于身份验证的令牌。

    创建一个组合的令牌:

    base64(用户名 + 过期时间 + 客户端的其他值 + 3des 编码(用户名、过期时间、源 ip、浏览器标识符、客户端的其他值))

    客户端可以使用令牌来验证请求,例如使用 JSON Web Token (RFC 7515)。

    在服务器端,用于 3des 编码的密钥可以作为令牌随时间旋转。每个请求都包含用于身份验证的令牌,每个响应都包含相同的令牌或过期前的新令牌。

    在这种情况下令牌包含用户名,因此请求身份验证只需要检查 3des 编码部分是否有效(与 相同,请求 ip 的来源相同。在这种情况下,如果有人窃取了令牌的可用性令牌作为会话ID受到更多限制。您可以将其他标识符组成为令牌,例如浏览器等。更难以伪造请求,因为攻击者必须伪造更多东西 - 这对他来说是未知的,因为他不知道发生了什么令牌的编码部分。(事实上没有完美的安全性,只会让破解变得更难)

    此解决方案的优点是:

    • 每个部分都是标准的,但不是整体,攻击者必须知道实现细节才能进行攻击。
    • 客户端可以使用令牌的一部分来显示来自令牌的信息,而令牌本身是安全的,因为每个未加密的部分都包含在加密部分中 - 因此如果服务器端令牌无效就无法修改 - 所以很容易检测攻击。
    • 集群不需要会话复制/粘性会话。 3des 密钥足以在节点之间复制 - 因此它适用于无状态后端策略。

    缺点是

    • 更难在服务器端实现,因为对于这个解决方案,必须在服务器端实现令牌生成/验证算法。推荐使用服务器过滤器。

    • 客户端必须实现令牌存储——推荐使用浏览器会话存储而不是 cookie——更容易窃取 cookie。

    • 必须确保 3des 密钥足够安全 - 建议使用 Java 安全性以避免妥协。

    【讨论】:

      【解决方案5】:
      public class SecureTokenGenerator {
      public static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      
      // 2048 bit keys should be secure until 2030 - https://web.archive.org/web/20170417095741/https://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm
      public static final int SECURE_TOKEN_LENGTH = 256;
      
      private static final SecureRandom random = new SecureRandom();
      
      private static final char[] symbols = CHARACTERS.toCharArray();
      
      private static final char[] buf = new char[SECURE_TOKEN_LENGTH];
      
      /**
       * Generate the next secure random token in the series.
       */
      public static String nextToken() {
          for (int idx = 0; idx < buf.length; ++idx)
              buf[idx] = symbols[random.nextInt(symbols.length)];
          return new String(buf);
      }
      

      }

      取自https://stackoverflow.com/a/41156/584947并显着浓缩

      【讨论】:

        【解决方案6】:

        创建一个独特的令牌,它完全基于您使用的逻辑和多少参数。 SupplierJava 8的功能接口帮助你:

        Supplier<String> tokenSupplier = () -> {
        
                StringBuilder token = new StringBuilder();
                long currentTimeInMilisecond = Instant.now().toEpochMilli();
                return token.append(currentTimeInMilisecond).append("-")
                        .append(UUID.randomUUID().toString()).toString();
        };
        
        System.out.println(tokenSupplier.get());
        

        输出:

        1591457374665-d5eff25f-b083-41c3-a90d-a89adcc45043
        

        您可以在这里了解更多信息-Java Token

        【讨论】:

          【解决方案7】:

          REST 基于 HTTP,鼓励使用底层协议而不是重新发明轮子。 HTTP 使用 cookie 来支持有状态的交互,例如记住身份验证,并且还支持用户名和密码身份验证。

          此外,Java EE 开箱即用地支持所有这些。查看教程

          http://docs.oracle.com/javaee/6/tutorial/doc/bncas.html

          【讨论】:

          • -1 抱歉,但问题是如果我可以使用我知道我可以使用的 cookie,我如何才能创建基于令牌的身份验证?
          • -1 完全...“我想使用身份验证令牌从移动设备登录”移动设备,而不是 Web 客户端。
          • @Andrew 我不明白你的反对意见。移动设备不仅能够使用 HTTP:这是它们通信的主要方式。他们可以而且应该将 cookie 保存为令牌。
          • 当会话不会自动过期时,为什么要在移动应用程序上实现 cookie,并且在客户端和服务器的响应正文中设置一个身份验证令牌要简单得多。此外,正如 Spring 所说,他对以一般方式创建身份验证令牌感兴趣,而不是如何实际发送该令牌(通过 cookie 或基本或其他方式)。我也对创建身份验证令牌的最佳方式感兴趣,而不是如何在请求中实际发送它。即使在我的情况下,就像我说的那样,将它发送到正文中要简单得多,因为我也将它保存在数据库端。
          • @artbristol 有一些安全原因需要牢记。将令牌存储在 cookie 中并不是客户端最安全的方式,因为可能会发生会话劫持 - 一些注入的 javascript 可以获取您的会话令牌。更安全的方法是(但也不完美)使用令牌作为 HTTP 头参数并存储在浏览器的会话存储中。因此,在安全上下文中,自己的安全协议作为基于标准的 java cookie 更安全。我建议令牌存储一些 CRC 校验和和过期。更多信息请查看:en.wikipedia.org/wiki/JSON_Web_Token
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-09-20
          • 2016-09-07
          • 1970-01-01
          • 2016-09-11
          • 2016-03-08
          • 2019-10-25
          • 1970-01-01
          相关资源
          最近更新 更多