Attribute构造函数需常量表达式:JSON加密动态密钥适配咨询
我用Json.NET实现JSON属性加密功能,原本用Const常量作为加密密钥,现在想改用应用初始化时用户输入的唯一token作为密钥。但把标记加密属性的代码从:
<JsonProperty("endpoint")> <JsonEncrypt(EndpointKey)>
改成:
<JsonProperty("endpoint")> <JsonEncrypt(Settings.AppTokens("myapp"))>
(AppTokens是Settings类的Dictionary(Of String, String)属性,从基础JSON配置文件读取)时,IDE提示**"Constant expression is required"**错误。
我的JsonEncryptAttribute类定义如下:
<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Field, AllowMultiple:=False)> Public NotInheritable Class JsonEncryptAttribute Inherits Attribute Public Property EncryptionKey As Byte() Public Sub New() Me.EncryptionKey = GenerateKey(DefaultKey) End Sub Public Sub New(ByVal Password As String) Me.EncryptionKey = GenerateKey(Password) End Sub Private Function GenerateKey(ByVal Password As String) As Byte() Return SHA256.HashData(Encoding.UTF8.GetBytes(Password)) End Function End Class
我曾尝试用类似<JsonEncrypt(GetType(Settings), NameOf(Settings.AppTokens("myapp")))>的写法,但未适配成功。
问题
- 该错误产生的原因是什么?是否与
JsonEncryptAttribute继承自System.Attribute有关? - 有无可行的替代方案,能够兼容从JSON配置读取的动态密钥?
问题1:错误原因
是的,这个错误和JsonEncryptAttribute继承自System.Attribute直接相关。.NET框架要求属性的构造函数参数必须是编译时常量——因为属性属于编译期元数据,会被嵌入程序集,无法在运行时动态计算参数值。
你之前用的EndpointKey是Const常量,属于编译时常量,符合要求;而Settings.AppTokens("myapp")是运行时才能获取的动态值(从配置文件读取、字典取值都是运行时操作),不符合属性构造参数的要求,因此触发错误。
问题2:替代方案
要支持动态密钥,需要调整加密逻辑,不再把密钥硬编码到属性元数据里,而是在序列化/反序列化过程中动态传入密钥。以下是两种可行方案:
方案1:自定义ContractResolver,结合属性标记动态获取密钥
- 修改
JsonEncryptAttribute,去掉构造函数的密钥参数,只保留标记作用(可添加密钥标识):
<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Field, AllowMultiple:=False)> Public NotInheritable Class JsonEncryptAttribute Inherits Attribute ' 指定对应配置中的密钥名称 Public Property KeyName As String End Class
- 标记属性时指定密钥名称:
<JsonProperty("endpoint")> <JsonEncrypt(KeyName:="myapp")>
- 自定义
ContractResolver,在创建属性序列化器时动态获取密钥:
Public Class EncryptedContractResolver Inherits DefaultContractResolver Protected Overrides Function CreateProperty(member As MemberInfo, memberSerialization As MemberSerialization) As JsonProperty Dim propertyObj = MyBase.CreateProperty(member, memberSerialization) Dim encryptAttr = member.GetCustomAttribute(Of JsonEncryptAttribute)() If encryptAttr IsNot Nothing Then ' 从Settings动态获取对应密钥 Dim key = Settings.AppTokens(encryptAttr.KeyName) Dim encryptionKey = SHA256.HashData(Encoding.UTF8.GetBytes(key)) ' 替换属性的序列化/反序列化逻辑 propertyObj.ValueProvider = New EncryptedValueProvider(propertyObj.ValueProvider, encryptionKey) End If Return propertyObj End Function End Class
- 实现
EncryptedValueProvider处理加密解密逻辑:
Public Class EncryptedValueProvider Implements IValueProvider Private ReadOnly _innerProvider As IValueProvider Private ReadOnly _encryptionKey As Byte() Public Sub New(innerProvider As IValueProvider, encryptionKey As Byte()) _innerProvider = innerProvider _encryptionKey = encryptionKey End Sub Public Sub SetValue(target As Object, value As Object) Implements IValueProvider.SetValue Dim encryptedStr = TryCast(value, String) If encryptedStr IsNot Nothing Then Dim decryptedValue = Decrypt(encryptedStr, _encryptionKey) _innerProvider.SetValue(target, decryptedValue) Else _innerProvider.SetValue(target, value) End If End Sub Public Function GetValue(target As Object) As Object Implements IValueProvider.GetValue Dim originalValue = _innerProvider.GetValue(target) If originalValue IsNot Nothing Then Return Encrypt(originalValue.ToString(), _encryptionKey) End If Return originalValue End Function ' 补充你的加密解密实现(复用原示例逻辑即可) Private Function Encrypt(input As String, key As Byte()) As String ' ... End Function Private Function Decrypt(input As String, key As Byte()) As String ' ... End Function End Class
- 序列化/反序列化时使用该
ContractResolver:
Dim jsonSettings = New JsonSerializerSettings() With { .ContractResolver = New EncryptedContractResolver() } ' 序列化 Dim jsonStr = JsonConvert.SerializeObject(yourObject, jsonSettings) ' 反序列化 Dim targetObj = JsonConvert.DeserializeObject(Of YourClass)(jsonStr, jsonSettings)
方案2:使用JsonConverter通过上下文传递密钥
- 保留
JsonEncryptAttribute作为标记,自定义JsonConverter:
Public Class EncryptedConverter Inherits JsonConverter Private ReadOnly _keyGetter As Func(Of String, Byte()) Public Sub New(keyGetter As Func(Of String, Byte())) _keyGetter = keyGetter End Sub Public Overrides Function CanConvert(objectType As Type) As Boolean Return True End Function Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer) Dim prop = serializer.Context.Context As JsonProperty Dim encryptAttr = prop.AttributeProvider.GetAttributes(GetType(JsonEncryptAttribute), False).FirstOrDefault() As JsonEncryptAttribute If encryptAttr IsNot Nothing Then Dim key = _keyGetter(encryptAttr.KeyName) writer.WriteValue(Encrypt(value.ToString(), key)) Else writer.WriteValue(value) End If End Sub Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object Dim prop = serializer.Context.Context As JsonProperty Dim encryptAttr = prop.AttributeProvider.GetAttributes(GetType(JsonEncryptAttribute), False).FirstOrDefault() As JsonEncryptAttribute If encryptAttr IsNot Nothing Then Dim encryptedStr = reader.Value.ToString() Dim key = _keyGetter(encryptAttr.KeyName) Return Decrypt(encryptedStr, key) Else Return reader.Value End If End Function ' 补充加密解密实现 Private Function Encrypt(input As String, key As Byte()) As String ' ... End Function Private Function Decrypt(input As String, key As Byte()) As String ' ... End Function End Class
- 序列化时传入密钥获取逻辑:
Dim converter = New EncryptedConverter(Function(keyName) Return SHA256.HashData(Encoding.UTF8.GetBytes(Settings.AppTokens(keyName))) End Function) Dim jsonSettings = New JsonSerializerSettings() With { .Converters = {converter} } ' 后续序列化/反序列化操作使用jsonSettings即可
这两种方案都能避开属性构造参数必须为常量的限制,实现动态从配置读取密钥进行加密解密。
内容的提问来源于stack exchange,提问作者G_Hosa_Phat

