引入redis服务,支持自动化单元测试
大石头 编写于 2022-03-31 22:56:30
X
using System;
using System.IO;
using NewLife.Security;

namespace NewLife.Web
{
    /// <summary>令牌提供者</summary>
    /// <remarks>
    /// 文档 https://www.yuque.com/smartstone/nx/token_provider
    /// </remarks>
    public class TokenProvider
    {
        #region 属性
        /// <summary>密钥。签发方用私钥,验证方用公钥</summary>
        public String Key { get; set; }
        #endregion

        #region 方法
        /// <summary>读取密钥</summary>
        /// <param name="file">文件</param>
        /// <param name="generate">是否生成</param>
        /// <returns></returns>
        public Boolean ReadKey(String file, Boolean generate = false)
        {
            if (file.IsNullOrEmpty()) return false;

            file = file.GetBasePath();
            if (File.Exists(file))
            {
                Key = File.ReadAllText(file);

                if (!Key.IsNullOrEmpty()) return true;
            }

            if (!generate || !file.EndsWithIgnoreCase(".prvkey")) return false;

            var ss = DSAHelper.GenerateKey();
            File.WriteAllText(file.EnsureDirectory(true), ss[0]);
            file = Path.ChangeExtension(file, ".pubkey");
            File.WriteAllText(file, ss[1]);

            Key = ss[0];

            return true;
        }

        /// <summary>编码用户和有效期得到令牌</summary>
        /// <param name="user">用户</param>
        /// <param name="expire">有效期</param>
        /// <returns></returns>
        public String Encode(String user, DateTime expire)
        {
            if (user.IsNullOrEmpty()) throw new ArgumentNullException(nameof(user));
            if (expire.Year < 2000) throw new ArgumentOutOfRangeException(nameof(expire));
            if (Key.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Key));

            var secs = expire.ToInt();

            // 拼接数据并签名
            var data = (user + "," + secs).GetBytes();
            var sig = DSAHelper.Sign(data, Key);

            // Base64拼接数据和签名
            return data.ToUrlBase64() + "." + sig.ToUrlBase64();
        }

        /// <summary>令牌解码得到用户和有效期</summary>
        /// <param name="token">令牌</param>
        /// <param name="expire">有效期</param>
        /// <returns></returns>
        [Obsolete("=>TryDecode")]
        public String Decode(String token, out DateTime expire)
        {
            if (token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(token));
            if (Key.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Key));

            expire = DateTime.MinValue;

            // Base64拆分数据和签名
            var p = token.IndexOf('.');
            var data = token[..p].ToBase64();
            var sig = token[(p + 1)..].ToBase64();

            // 验证签名
            //if (!DSAHelper.Verify(data, Key, sig)) throw new InvalidOperationException("签名验证失败!");
            if (!DSAHelper.Verify(data, Key, sig)) return null;

            // 拆分数据和有效期
            var str = data.ToStr();
            p = str.LastIndexOf(',');

            var user = str[..p];
            var secs = str[(p + 1)..].ToInt();
            expire = secs.ToDateTime();

            return user;
        }

        /// <summary>尝试解码令牌,即使失败,也会返回用户信息和有效时间</summary>
        /// <param name="token">令牌</param>
        /// <param name="user">用户信息</param>
        /// <param name="expire">有效时间</param>
        /// <returns>解码结果,成功或失败</returns>
        public Boolean TryDecode(String token, out String user, out DateTime expire)
        {
            if (token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(token));
            //if (Key.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Key));

            // Base64拆分数据和签名
            var p = token.IndexOf('.');
            var data = token[..p].ToBase64();
            var sig = token[(p + 1)..].ToBase64();

            // 拆分数据和有效期
            var str = data.ToStr();
            p = str.LastIndexOf(',');

            user = str[..p];
            var secs = str[(p + 1)..].ToInt();
            expire = secs.ToDateTime();

            if (Key.IsNullOrEmpty()) return false;

            // 验证签名
            //if (!DSAHelper.Verify(data, Key, sig)) throw new InvalidOperationException("签名验证失败!");
            if (!DSAHelper.Verify(data, Key, sig)) return false;

            return true;
        }
        #endregion
    }
}