Previous topicNext topic
Help > 开发指南 > API对接 > HttpServer > 事件 > 常见的 WebAPI 验证权限的方案 >
授权-自定义权限验证

我们这里写一个针对后台API对接场景的授权。授权的设计思路,在API访问之前先做授权验证,对那些需要授权验证的API接口进行验证。如果验证失败则拒绝执行API。授权的思路是,给每个授权的账号发一个Key,客户端在对接需要授权的API接口时,需要传递两个参数,一个是appAccessId,另一个是sign。用户在传递参数时,需要将除了sign参数外的所有参数按照Key值进行排序,然后拼接成键值对的形式,例如:appAccessId=fdifdfikjdfaf&index=0&page=2,键值对的Value值不需要进行UrlEncode加密,然后与发放的appAccessKey值进行字符串连接,最后算一个MD5值,最后将这个验证码放到sign参数中进行请求API接口数据。只要我们将appAccessKey值保护的足够好,这种设计就会变得简单、高效、安全。

下面的代码需要我们先在自己的数据库里面创建一个appAccessInfo表,包含appAccessId,appAccessKey字段。

Vb.Net
Dim e As SmHttpRequestEventArgs=Args(0)
'获得路由是否需要需要权限验证
If Not Proj.ExtendedProperties.ContainsKey("API是否需要权限验证字典") Then
    '如果不存在此数据,则添加
    Dim dic As New Concurrent.ConcurrentDictionary(Of String,String)
    Proj.ExtendedProperties("API是否需要权限验证字典")=dic
End If
Dim dicAuth As Concurrent.ConcurrentDictionary(Of String,String)=Proj.ExtendedProperties("API是否需要权限验证字典")
If dicAuth.Count<=0 Then
    Dim dic As Dictionary(Of String,Boolean)=Proj.SysDataFactory("ProjectsFile").GetDictionaryBySQL(Of String,Boolean)("Select Path,CheckAuthentication from SysHttpServerFuncs where Path Is Not null And Path<>''")
    For Each item As KeyValuePair(Of String,Boolean) In dic
        dicAuth.TryAdd(item.Key,item.Value)
    Next
End If
Dim blnNeedCheck As Boolean
If dicAuth.TryGetValue(e.Path,blnNeedCheck) Then
    If Not blnNeedCheck Then
        '如果不需要检验,则直接返回并继续执行API
        Return True
    End If
Else
    '如果不包含在路径里面,则不做权限验证
    Return True
End If

'用户App密钥字典
If Not Proj.ExtendedProperties.ContainsKey("用户App密钥字典") Then
    '如果不存在此数据,则添加
    Dim dic As New Concurrent.ConcurrentDictionary(Of String,String)
    Proj.ExtendedProperties("用户App密钥字典")=dic
End If
Dim dicKey As Concurrent.ConcurrentDictionary(Of String,String)=Proj.ExtendedProperties("用户App密钥字典")
If dicKey.Count<=0 Then
    Dim dic As Dictionary(Of String,String)=Proj.SysDataFactory("UserDB").GetDictionaryBySQL(Of String,String)("select appAccessId,appAccessKey from appAccessInfo")
    For Each item As KeyValuePair(Of String,String) In dic
        dicKey.TryAdd(item.Key,item.Value)
    Next
End If
'根据API支持的方法不同,可以到e.GetValues和e.PostValues里面获得相应的传递参数
If Not e.Values.ContainsKey("appAccessId") Then
    '如果这个接口中不包含appAccessId参数名,则认为不符合我们的规定(假定所有需要验证的接口都必须要传递appAccessId这个参数,因为需要对它进行验证)
    Dim obj As New ReturnedObject
    obj.Code=406
    '返回API执行失败的结果
    obj.Successed=False
    '可以根据需要将相应的信息返回给客户端
    obj.Message="接口缺少必备参数appAccessId"
    e.SendObject(obj)
    Return False
End If
If Not e.Values.ContainsKey("sign") Then
    '如果这个接口中不包含sign参数名,则认为不符合我们的规定(假定所有需要验证的接口都必须要传递sign这个参数,因为需要对它进行验证)
    Dim obj As New ReturnedObject
    obj.Code=407
    '返回API执行失败的结果
    obj.Successed=False
    '可以根据需要将相应的信息返回给客户端
    obj.Message="接口缺少必备参数sign"
    e.SendObject(obj)
    Return False
End If
Dim strAppAccessId As String=e.Values("appAccessId")
Dim strAppAccessKey As String
If dicKey.TryGetValue(strAppAccessId,strAppAccessKey) Then
    '获得用户的密钥,然后检验传递进来的数据是否合法
    Dim dicSort As New SortedDictionary(Of String,String)
    For Each item As KeyValuePair(Of String,String) In e.Values
        If item.Key<>"sign" Then '将非验证字段的其他项都添加到排序字典中进行排序
            dicSort.Add(item.Key,item.Value)
        End If
    Next
    '将字典转换成文本
    Dim strPostData As String=NetHelp.ConvertDictionaryToPostData(dicSort,False)
    '获得传递数据的验证码
    Dim strSign As String=Crypt.MD5ForString(strPostData & strAppAccessKey)
    If strSign<> e.Values("sign") Then
        Dim obj As New ReturnedObject
        obj.Code=409
        '返回API执行失败的结果
        obj.Successed=False
        '可以根据需要将相应的信息返回给客户端
        obj.Message="数据验证失败"
        e.SendObject(obj)
        Return False
    End If
    '数据验证成功,说明发送请求的是合法用户,通知程序可以继续执行后面的WebAPI程序
    Return True
Else
    '如果找不到用户授权
    Dim obj As New ReturnedObject
    obj.Code=408
    '返回API执行失败的结果
    obj.Successed=False
    '可以根据需要将相应的信息返回给客户端
    obj.Message="未找到授权用户信息"
    e.SendObject(obj)
    Return False
End If

C#
SmHttpRequestEventArgs e = (SmHttpRequestEventArgs)Args[0];

//获得路由是否需要需要权限验证
if (!Proj.ExtendedProperties.ContainsKey("API是否需要权限验证字典"))
{
    //如果不存在此数据,则添加
    var dic = new ConcurrentDictionary<string, string>();
    Proj.ExtendedProperties["API是否需要权限验证字典"] = dic;
}
var dicAuth = (ConcurrentDictionary<string, string>)Proj.ExtendedProperties["API是否需要权限验证字典"];
if (dicAuth.Count <= 0)
{
    var dic = Proj.SysDataFactory["ProjectsFile"].GetDictionaryBySQL<string, bool>("Select Path,CheckAuthentication from SysHttpServerFuncs where Path Is Not null And Path<>''");
    foreach (var item in dic)
    {
        dicAuth.TryAdd(item.Key, item.Value);
    }
}
bool blnNeedCheck;
if (dicAuth.TryGetValue(e.Path, out blnNeedCheck))
{
    if (!blnNeedCheck)
    {
        //如果不需要检验,则直接返回并继续执行API
        return true;
    }
}
else
{
    //如果不包含在路径里面,则不做权限验证
    return true;
}

//用户App密钥字典
if (!Proj.ExtendedProperties.ContainsKey("用户App密钥字典"))
{
    //如果不存在此数据,则添加
    var dic = new ConcurrentDictionary<string, string>();
    Proj.ExtendedProperties["用户App密钥字典"] = dic;
}
var dicKey = (ConcurrentDictionary<string, string>)Proj.ExtendedProperties["用户App密钥字典"];
if (dicKey.Count <= 0)
{
    var dic = Proj.SysDataFactory["UserDB"].GetDictionaryBySQL<string, string>("select appAccessId,appAccessKey from appAccessInfo");
    foreach (var item in dic)
    {
        dicKey.TryAdd(item.Key, item.Value);
    }
}
//根据API支持的方法不同,可以到e.GetValues和e.PostValues里面获得相应的传递参数
if (!e.Values.ContainsKey("appAccessId"))
{
    //如果这个接口中不包含appAccessId参数名,则认为不符合我们的规定(假定所有需要验证的接口都必须要传递appAccessId这个参数,因为需要对它进行验证)
    var obj = new ReturnedObject();
    obj.Code = 406;
    //返回API执行失败的结果
    obj.Successed = false;
    //可以根据需要将相应的信息返回给客户端
    obj.Message = "接口缺少必备参数appAccessId";
    e.SendObject(obj);
    return false;
}
if (!e.Values.ContainsKey("sign"))
{
    //如果这个接口中不包含sign参数名,则认为不符合我们的规定(假定所有需要验证的接口都必须要传递sign这个参数,因为需要对它进行验证)
    var obj = new ReturnedObject();
    obj.Code = 407;
    //返回API执行失败的结果
    obj.Successed = false;
    //可以根据需要将相应的信息返回给客户端
    obj.Message = "接口缺少必备参数sign";
    e.SendObject(obj);
    return false;
}
string strAppAccessId = e.Values["appAccessId"];
string strAppAccessKey;
if (dicKey.TryGetValue(strAppAccessId, out strAppAccessKey))
{
    //获得用户的密钥,然后检验传递进来的数据是否合法
    var dicSort = new SortedDictionary<string, string>();
    foreach (var item in e.Values)
    {
        if (item.Key != "sign") //将非验证字段的其他项都添加到排序字典中进行排序
        {
            dicSort.Add(item.Key, item.Value);
        }
    }
    //将字典转换成文本
    string strPostData = NetHelp.ConvertDictionaryToPostData(dicSort, false);
    //获得传递数据的验证码
    string strSign = Crypt.MD5ForString(strPostData + strAppAccessKey);
    if (strSign != e.Values["sign"])
    {
        var obj = new ReturnedObject();
        obj.Code = 409;
        //返回API执行失败的结果
        obj.Successed = false;
        //可以根据需要将相应的信息返回给客户端
        obj.Message = "数据验证失败";
        e.SendObject(obj);
        return false;
    }
    //数据验证成功,说明发送请求的是合法用户,通知程序可以继续执行后面的WebAPI程序
    return true;
}
else
{
    //如果找不到用户授权
    var obj = new ReturnedObject();
    obj.Code = 408;
    //返回API执行失败的结果
    obj.Successed = false;
    //可以根据需要将相应的信息返回给客户端
    obj.Message = "未找到授权用户信息";
    e.SendObject(obj);
    return false;
}