49

我有一个 WCF Webservice 来检查用户是否有效。

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

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

        return true;
    }
    else 
    {
        return false;
    }
}   
4

6 回答 6

156

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

要创建唯一令牌:

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());

要解码令牌以获取创建时间:

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

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

于 2013-02-01T10:34:11.267 回答
35

我喜欢 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 小时左右。这是我的好案例单元测试:

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");
}

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

快乐编码

沃尔特

于 2015-11-23T14:53:35.583 回答
3

用于Dictionary<string, DateTime>存储带有时间戳的令牌:

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);
    }

因此,当您验证令牌时,只需检查令牌是否存在于 dic 中。

于 2013-02-01T10:20:02.927 回答
0

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

要生成密钥,请参阅此处

于 2013-02-01T10:06:03.163 回答
0

我也有类似但略有不同的问题。我需要一个可以在 2 分钟内用在不同服务器上的令牌。ServerA 和 serverB 共享一个私钥。当然,在用户浏览器中,令牌是公开的。

在此处输入图像描述

Web 服务器 A 创建一个包含日期时间的字符串。然后它对字符串进行哈希处理。浏览器中的用户使用令牌。当服务器 B 收到令牌时,会执行相同的哈希并比较结果。ServerA 和 serverB 运行 c# 代码。

问题相似但不一样,但也许代码无论如何都可以帮助某人......

public class TokenHelper
{

    private static byte[] GetHash(string inputString)
    {
        using (HashAlgorithm algorithm = MD5.Create())
            return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
    }

    private static string GetHashString(string inputString)
    {
        StringBuilder sb = new StringBuilder();
        foreach (byte b in GetHash(inputString))
            sb.Append(b.ToString("X2"));

        return sb.ToString();
    }

    public static Guid GetTokenForGuest(EnumWebsiteName website, string privateKey)
    {            
        string datetime = DateTime.Now.ToString("dd/MM/yyyy hh:mm");
        var hashString = GetHashString($"{datetime}|{website}|{privateKey}");            
        return new Guid(hashString);
    }

    public static bool  CheckTokenForGuest(Guid token, EnumWebsiteName website, string privateKey)
    {
        string datetime = DateTime.Now.ToString("dd/MM/yyyy hh:mm");
        var hashString = GetHashString($"{datetime}|{website}|{privateKey}");
        var test1 = new Guid(hashString);
        if (test1.CompareTo(token)==0) {
            return true;
        }

        string datetime2 = DateTime.Now.AddMinutes(-1).ToString("dd/MM/yyyy hh:mm");
        var hashString2 = GetHashString($"{datetime2}|{website}|{privateKey}");
        var test2 = new Guid(hashString2);
        if (test2.CompareTo(token) == 0)
        {
            return true;
        }

        return false;
    }

}
于 2022-02-19T20:15:38.160 回答
-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;
}
于 2020-07-05T18:11:28.300 回答