【问题标题】:how to generate a unique token which expires after 24 hours?如何生成 24 小时后过期的唯一令牌?
【发布时间】:2013-01-16 14:44:16
【问题描述】:

我有一个 WCF Web 服务来检查用户是否有效。

如果用户有效,我想生成一个 24 小时后过期的令牌。

public bool authenticateUserManual(string userName, string password,string language,string token)
{
    if (Membership.ValidateUser(userName,password))
    {
        //////////
        string token = ???? 
        //////////

        return true;
    }
    else 
    {
        return false;
    }
}   

【问题讨论】:

  • 生成一个token并与时间戳一起存储,下次检查是否在24小时内使用,否则显示过期消息
  • 你能给我一个简短的例子吗?我不知道如何生成唯一令牌

标签: c# asp.net wcf authentication token


【解决方案1】:

这样,令牌最多可以存在 24 小时。这是生成令牌的代码,有效期最长为 24 小时。我们使用的这段代码,但我没有编写它。

public static string GenerateToken()
{
    int month = DateTime.Now.Month;
    int day = DateTime.Now.Day;
    string token = ((day * 100 + month) * 700 + day * 13).ToString();
    return token;
}

【讨论】:

    【解决方案2】:

    我喜欢 Guffa 的回答,由于无法发表评论,我将在此处提供 Udil 的问题的答案。

    我需要类似的东西,但我想在我的令牌中加入某些逻辑,我想:

    1. 查看令牌的到期情况
    2. 使用 guid 屏蔽验证(全局应用程序 guid 或用户 guid)
    3. 查看令牌是否是为我创建它的目的而提供的(不可重复使用..)
    4. 查看我向其发送令牌的用户是否是我为其验证它的用户

    现在第 1-3 点是固定长度的,所以很简单,这是我的代码:

    这是我生成令牌的代码:

    public string GenerateToken(string reason, MyUser user)
    {
        byte[] _time     = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());
        byte[] _key      = Guid.Parse(user.SecurityStamp).ToByteArray();
        byte[] _Id       = GetBytes(user.Id.ToString());
        byte[] _reason   = GetBytes(reason);
        byte[] data       = new byte[_time.Length + _key.Length + _reason.Length+_Id.Length];
    
        System.Buffer.BlockCopy(_time, 0, data, 0, _time.Length);
        System.Buffer.BlockCopy(_key , 0, data, _time.Length, _key.Length);
        System.Buffer.BlockCopy(_reason, 0, data, _time.Length + _key.Length, _reason.Length);
        System.Buffer.BlockCopy(_Id, 0, data, _time.Length + _key.Length + _reason.Length, _Id.Length);
    
        return Convert.ToBase64String(data.ToArray());
    }
    

    这是我的代码,用于获取生成的令牌字符串并对其进行验证:

    public TokenValidation ValidateToken(string reason, MyUser user, string token)
    {
        var result = new TokenValidation();
        byte[] data     = Convert.FromBase64String(token);
        byte[] _time     = data.Take(8).ToArray();
        byte[] _key      = data.Skip(8).Take(16).ToArray();
        byte[] _reason   = data.Skip(24).Take(2).ToArray();
        byte[] _Id       = data.Skip(26).ToArray();
    
        DateTime when = DateTime.FromBinary(BitConverter.ToInt64(_time, 0));
        if (when < DateTime.UtcNow.AddHours(-24))
        {
            result.Errors.Add( TokenValidationStatus.Expired);
        }
        
        Guid gKey = new Guid(_key);
        if (gKey.ToString() != user.SecurityStamp)
        {
            result.Errors.Add(TokenValidationStatus.WrongGuid);
        }
    
        if (reason != GetString(_reason))
        {
            result.Errors.Add(TokenValidationStatus.WrongPurpose);
        }
    
        if (user.Id.ToString() != GetString(_Id))
        {
            result.Errors.Add(TokenValidationStatus.WrongUser);
        }
        
        return result;
    }
    
    private static string GetString(byte[] reason) => Encoding.ASCII.GetString(reason);
    
    private static byte[] GetBytes(string reason) => Encoding.ASCII.GetBytes(reason);
    

    TokenValidation 类如下所示:

    public class TokenValidation
    {
        public bool Validated { get { return Errors.Count == 0; } }
        public readonly List<TokenValidationStatus> Errors = new List<TokenValidationStatus>();
    }
    
    public enum TokenValidationStatus
    {
        Expired,
        WrongUser,
        WrongPurpose,
        WrongGuid
    }
    

    现在我有一种简单的方法来验证令牌,无需将其保留在列表中 24 小时左右。 这是我的 Good-Case 单元测试:

    private const string ResetPasswordTokenPurpose = "RP";
    private const string ConfirmEmailTokenPurpose  = "EC";//change here change bit length for reason  section (2 per char)
    
    [TestMethod]
    public void GenerateTokenTest()
    {
        MyUser user         = CreateTestUser("name");
        user.Id             = 123;
        user.SecurityStamp  = Guid.NewGuid().ToString();
        var token   = sit.GenerateToken(ConfirmEmailTokenPurpose, user);
        var validation    = sit.ValidateToken(ConfirmEmailTokenPurpose, user, token);
        Assert.IsTrue(validation.Validated,"Token validated for user 123");
    }
    

    人们可以轻松地为其他业务案例调整代码。

    快乐编码

    沃尔特

    【讨论】:

    • 这是一个非常酷的答案;)为了适应我的逻辑,我做了一些调整。
    • GetString() 怎么样,能不能发一下正文?
    • @Jmocke 使用 GetString() 和 GetBytes() 将命名空间设置为 System.Text,然后使用 Encoding.UTF8.GetString() / Encoding.UTF8.GetBytes()
    • 我感觉这个例子完全省略了任何安全层。似乎没有加密签名允许令牌通过不安全的通道传输并确保不被篡改。在我看来,任何看到这个令牌的人都可以创建它的有效变体,这些变体将通过验证。 注意,这个答案似乎不安全!
    • 您能详细解释一下您是如何跳过和获取数据的吗?
    【解决方案3】:

    有两种可能的方法;您可以创建一个唯一值并将其与创建时间一起存储在某个地方,例如在数据库中,或者您将创建时间放在令牌中,以便稍后对其进行解码并查看它的创建时间。

    创建唯一令牌:

    string token = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
    

    创建包含时间戳的唯一令牌的基本示例:

    byte[] time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());
    byte[] key = Guid.NewGuid().ToByteArray();
    string token = Convert.ToBase64String(time.Concat(key).ToArray());
    

    解码token获取创建时间:

    byte[] data = Convert.FromBase64String(token);
    DateTime when = DateTime.FromBinary(BitConverter.ToInt64(data, 0));
    if (when < DateTime.UtcNow.AddHours(-24)) {
      // too old
    }
    

    注意:如果您需要带有时间戳的令牌是安全的,您需要对其进行加密。否则,用户可能会弄​​清楚它包含什么并创建一个错误的令牌。

    【讨论】:

    • 如何从数据变量中提取令牌?
    • @UdiI:当data 变量是从令牌创建时,您为什么要这样做?无论如何,您将使用 Convert.ToBase64String(data)data 变量重新创建令牌。
    • @UdiI:是的,令牌包括时间戳。您的意思是要获取 GUID 密钥吗?然后你会使用new GUID(data.Skip(8).ToArray())
    • 使用这个时间解决方案和这个加密msdn.microsoft.com/en-us/library/…你可以创建一个很好的安全令牌。
    • @Guffa 在令牌中使用 guid 是否安全
    【解决方案4】:

    使用Dictionary&lt;string, DateTime&gt; 存储带有时间戳的令牌:

    static Dictionary<string, DateTime> dic = new Dictionary<string, DateTime>();
    

    每当您创建新令牌时添加带有时间戳的令牌:

    dic.Add("yourToken", DateTime.Now);
    

    有一个计时器正在运行以从 dic 中删除任何过期的令牌:

     timer = new Timer(1000*60); //assume run in 1 minute
     timer.Elapsed += timer_Elapsed;
    
     static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            var expiredTokens = dic.Where(p => p.Value.AddDays(1) <= DateTime.Now)
                                  .Select(p => p.Key);
    
            foreach (var key in expiredTokens)
                dic.Remove(key);
        }
    

    所以,当你认证token时,只需检查token是否存在于dic中。

    【讨论】:

    • timer = new Timer(1000*60);这里有什么遗漏吗?
    • 缺少什么,使用 System.Timers.Timer
    【解决方案5】:

    您需要在为第一次注册创建时存储令牌。当您从登录表中检索数据时,如果输入日期超过 1 天(24 小时),您需要将输入的日期与当前日期区分开来,您需要显示消息,例如您的令牌已过期。

    要生成密钥,请参考here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-06
      • 2021-07-04
      • 2019-05-31
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      相关资源
      最近更新 更多