ASP.NET Core使用自定义验证属性控制访问权限详解

时间:2022-11-05 08:26:36

前言

大家都知道在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(AccessToken)的终端应用能访问我们的受控站点(如WebAPI站点),此时我们可以通过验证属性的方法来解决。

本文将详细介绍ASP.NET Core使用自定义验证属性控制访问权限的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

方法如下

一、public class Startup的配置:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//启用跨域访问(不同端口也是跨域)
services.AddCors(options =>
{
options.AddPolicy("AllowOriginOtherBis",
builder => builder.WithOrigins("https://1.16.9.12:4432", "https://pc12.ato.biz:4432", "https://localhost:44384", "https://1.16.9.12:4432", "https://pc12.ato.biz:4432").AllowAnyMethod().AllowAnyHeader());
});
 
//启用自定义属性以便对控制器或Action进行[TerminalApp()]定义。
 
services.AddSingleton<IAuthorizationHandler, TerminalAppAuthorizationHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("TerminalApp", policyBuilder =>
{
policyBuilder.Requirements.Add(new TerminalAppAuthorizationRequirement());
});
});

二、public void Configure(IApplicationBuilder app, IHostingEnvironment env)中的配置:

?
1
2
app.UseHttpsRedirection();  //使用Https传输
app.UseCors("AllowOriginOtherBis"); //根据定义启用跨域设置

三、示例WebApi项目结构:

 ASP.NET Core使用自定义验证属性控制访问权限详解

四、主要代码(我采用的从数据库进行验证):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
 internal class TerminalAppAttribute : AuthorizeAttribute
 {
  public string AppID { get; }
 
  /// <summary>
  /// 指定客户端访问API
  /// </summary>
  /// <param name="appID"></param>
  public TerminalAppAttribute(string appID="") : base("TerminalApp")
  {
   AppID = appID;
  }
 }
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
 {
  protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
  {
   var attributes = new List<TAttribute>();
 
   if ((context.Resource as AuthorizationFilterContext)?.ActionDescriptor is ControllerActionDescriptor action)
   {
    attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
    attributes.AddRange(GetAttributes(action.MethodInfo));
   }
 
   return HandleRequirementAsync(context, requirement, attributes);
  }
 
  protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);
 
  private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
  {
   return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
  }
 }
 
 internal class TerminalAppAuthorizationHandler : AttributeAuthorizationHandler<TerminalAppAuthorizationRequirement,TerminalAppAttribute>
 {
  protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TerminalAppAuthorizationRequirement requirement, IEnumerable<TerminalAppAttribute> attributes)
  {
   object errorMsg = string.Empty;
   //如果取不到身份验证信息,并且不允许匿名访问,则返回未验证403
   if (context.Resource is AuthorizationFilterContext filterContext &&
filterContext.ActionDescriptor is ControllerActionDescriptor descriptor)
   {
    //先判断是否是匿名访问,
    if (descriptor != null)
    {
     var actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
     bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
     //非匿名的方法,链接中添加accesstoken值
     if (isAnonymous)
     {
      context.Succeed(requirement);
      return Task.CompletedTask;
     }
     else
     {
      //url获取access_token
      //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
      var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;
      //var questUrl = httpContext.Request.Path.Value.ToLower();
      string requestAppID = httpContext.Request.Headers["appid"];
      string requestAccessToken = httpContext.Request.Headers["access_token"];
      if ((!string.IsNullOrEmpty(requestAppID)) && (!string.IsNullOrEmpty(requestAccessToken)))
      {
       if (attributes != null)
       {
        //当不指定具体的客户端AppID仅运用验证属性时默认所有客户端都接受
        if (attributes.ToArray().ToString()=="")
        {
         //任意一个在数据库列表中的App都可以运行,否则先判断提交的APPID与需要ID是否相符
         bool mat = false;
         foreach (var terminalAppAttribute in attributes)
         {
          if (terminalAppAttribute.AppID == requestAppID)
          {
           mat = true;
           break;
          }
         }
         if (!mat)
         {
          errorMsg = ReturnStd.NotAuthorize("客户端应用未在服务端登记或未被授权运用当前功能.");
          return HandleBlockedAsync(context, requirement, errorMsg);
         }
        }
       }
 
       //如果未指定attributes,则表示任何一个终端服务都可以调用服务, 在验证区域验证终端提供的ID是否匹配数据库记录
       string valRst = ValidateToken(requestAppID, requestAccessToken);
       if (string.IsNullOrEmpty(valRst))
       {
        context.Succeed(requirement);
        return Task.CompletedTask;
       }
       else
       {
        errorMsg = ReturnStd.NotAuthorize("AccessToken验证失败(" + valRst + ")","91");
        return HandleBlockedAsync(context, requirement, errorMsg);
       }
      }
      else
      {
       errorMsg = ReturnStd.NotAuthorize("未提供AppID或Token.");
       return HandleBlockedAsync(context, requirement, errorMsg);
       //return Task.CompletedTask;
      }
     }
    }
   }
   else
   {
    errorMsg = ReturnStd.NotAuthorize("FilterContext类型不匹配.");
    return HandleBlockedAsync(context, requirement, errorMsg);
   }
 
   errorMsg = ReturnStd.NotAuthorize("未知错误.");
   return HandleBlockedAsync(context,requirement, errorMsg);
  }
 
 
  //校验票据(数据库数据匹配)
  /// <summary>
  /// 验证终端服务程序提供的AccessToken是否合法
  /// </summary>
  /// <param name="appID">终端APP的ID</param>
  /// <param name="accessToken">终端APP利用其自身AppKEY运算出来的AccessToken,与服务器生成的进行比对</param>
  /// <returns></returns>
  private string ValidateToken(string appID,string accessToken)
  {
   try
   {
    DBContextMain dBContext = new DBContextMain();
    string appKeyOnServer = string.Empty;
    //从数据库读取AppID对应的KEY(此KEY为加解密算法的AES_KEY
    AuthApp authApp = dBContext.AuthApps.FirstOrDefault(a => a.AppID == appID);
    if (authApp == null)
    {
     return "客户端应用没有在云端登记!";
    }
    else
    {
     appKeyOnServer = authApp.APPKey;
    }
    if (string.IsNullOrEmpty(appKeyOnServer))
    {
     return "客户端应用基础信息有误!";
    }
 
    string tmpToken = string.Empty;
    tmpToken = System.Net.WebUtility.UrlDecode(accessToken);//解码相应的Token到原始字符(因其中可能会有+=等特殊字符,必须编码后传递)
    tmpToken = OCrypto.AES16Decrypt(tmpToken, appKeyOnServer); //使用APPKEY解密并分析
 
    if (string.IsNullOrEmpty(tmpToken))
    {
     return "客户端提交的身份令牌运算为空!";
    }
    else
    {
     try
     {
      //原始验证码为im_cloud_sv001-appid-ticks格式
      //取出时间,与服务器时间对比,超过10秒即拒绝服务
      long tmpTime =Convert.ToInt64(tmpToken.Substring(tmpToken.LastIndexOf("-")+1));
      //DateTime dt = DateTime.ParseExact(tmpTime, "yyyyMMddHHmmss", CultureInfo.CurrentCulture);
      DateTime dt= new DateTime(tmpTime);
      bool IsInTimeSpan = (Convert.ToDouble(ODateTime.DateDiffSeconds(dt, DateTime.Now)) <= 7200);
      bool IsInternalApp = (tmpToken.IndexOf("im_cloud_sv001-") >= 0);
      if (!IsInternalApp || !IsInTimeSpan)
      {
       return "令牌未被许可或已经失效!";
      }
      else
      {
       return string.Empty; //成功验证
      }
     }
     catch (Exception ex)
     {
      return "令牌解析出错(" + ex.Message + ")";
     }
 
    }
   }
   catch (Exception ex)
   {
    return "令牌解析出错(" + ex.Message + ")";
   }
  }
 
  private Task HandleBlockedAsync(AuthorizationHandlerContext context, TerminalAppAuthorizationRequirement requirement, object errorMsg)
  {
   var authorizationFilterContext = context.Resource as AuthorizationFilterContext;
   authorizationFilterContext.Result = new JsonResult(errorMsg) { StatusCode = 202 };
   //设置为403会显示不了自定义信息,改为Accepted202,由客户端处理
   context.Succeed(requirement);
   return Task.CompletedTask;
  }
 }
?
1
2
3
4
5
6
internal class TerminalAppAuthorizationRequirement : IAuthorizationRequirement
{
 public TerminalAppAuthorizationRequirement()
 {
 }
}

五、相应的Token验证代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[AutoValidateAntiforgeryToken] //在本控制器内自动启用跨站攻击防护
 [Route("api/get_accesstoken")]
 public class GetAccessTokenController : Controller
 {
  //尚未限制访问频率
  //返回{"access_token":"ACCESS_TOKEN","expires_in":7200} 有效期2个小时
  //错误时返回{"errcode":40013,"errmsg":"invalid appid"}
  [AllowAnonymous]
  public ActionResult<string> Get()
  {
   try
   {
    string tmpToken = string.Empty;
 
    string appID = HttpContext.Request.Headers["appid"];
    string appKey = HttpContext.Request.Headers["appkey"];
 
    if ((appID.Length < 5) || appKey.Length != 32)
    {
     return "{'errcode':10000,'errmsg':'appid或appkey未提供'}";
    }
    //token采用im_cloud_sv001-appid-ticks数字
    long timeTk = DateTime.Now.Ticks; //输出毫微秒:633603924670937500
             //DateTime dt = new DateTime(timeTk);//可以还原时间
 
    string plToken = "im_cloud1-" + appID + "-" + timeTk;
    tmpToken = OCrypto.AES16Encrypt(plToken, appKey); //使用APPKEY加密
 
    tmpToken = System.Net.WebUtility.UrlEncode(tmpToken);
    //编码相应的Token(因其中可能会有+=等特殊字符,必须编码后传递)
    tmpToken = "{'access_token':'" + tmpToken + "','expires_in':7200}";
    return tmpToken;
   }
   catch (Exception ex)
   {
    return "{'errcode':10001,'errmsg':'" + ex.Message +"'}";
   }
  }
 }
 
GetAccessTokenController.cs

六、这样,在我们需要控制的地方加上[TerminalApp()] 即可,这样所有授权的App都能访问,当然,也可以使用[TerminalApp(“app01”)]限定某一个ID为app01的应用访问。

?
1
2
3
4
5
6
7
[Area("SYS")]  // 路由: api/sys/user
 [Produces("application/json")]
 [TerminalApp()]
 public class UserController : Controller
{
//
}

 七、一个CS客户端通过Web API上传数据调用示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
string postURL = "http://sv12.ato.com/api/sys/user/postnew";
 
Dictionary<string, string> headerDic2 = new Dictionary<string, string>
{
 { "appid", MainFramework.CloudAppID },
 { "access_token", accessToken }
};
string pushRst = OPWeb.Post(postURL, headerDic2, "POST", sYS_Users);
if (string.IsNullOrEmpty(pushRst))
{
 MyMsg.Information("推送成功!");
}
else
{
 MyMsg.Information("推送失败!", pushRst);
}
?
1
2
3
4
5
6
string accessToken = MainFramework.CloudAccessToken;
if (accessToken.IndexOf("ERROR:") >= 0)
{
 MyMsg.Information("获取Token出错:" + accessToken);
 return;
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:http://www.cnblogs.com/imes/p/9808467.html