WebAPI授权认证注意事项

今天搞了一天的webapi授权,刚开始想用微软的Authorization去做,虽然强大的微软提供了多种验证方式,但是我还是没用。
原因是要实现微软定义的认证方式,服务端和客户端都需要实现太多了。如下图 

 

 

后来自己定了一个流程,即调用webapi的接口前,客户端登录一次,如下图

 

只是Token创建后,除了更新数据库对应的字段,同时也将Token存入缓存。后台有一个Token对象,这个对象包含了生成的Token值以及该Token对应的
客户端信息,。
然后实现的话也比较简单,继承AuthorizationFilterAttribute刷新器,重写OnAuthorization方法,在该方法中验证Token,失败则通知客户端。
本来这个思路真的很美好啊,但是我的webapi和mvc在同一个站点!!
同一个站点有个坑爹地方,就是我mvc已经实现了微软的认证方式,如下图

 

然后webapi只要认证未通过就会重定向到登录页面。。。。
我特么为了这个问题,困扰好久,将继承刷选器更新为继承DelegatingHandler,然并卵。。。
知道原因么?就是我在给response的StatusCode赋值不应该赋值HttpStatusCode.Unauthorized,因为给StatusCode赋值为HttpStatusCode.Unauthorized后,系统会走mvc的认证失败流程。。。

 

好了,唠叨完了,直接看代码,下面是登录的Webapi

 1         [HttpPost]
 2         [SwaggerActionFilter]
 3         [NoAuthentication]  
 4         public IHttpActionResult Validate(dynamic post)
 5         {
 6             try
 7             {
 8                 
 9                 string orgCode = post.orgCode;
10                 string userName = post.userName;
11                 string password = post.password;
12                 if (string.IsNullOrEmpty(orgCode))
13                     return Json(new BaseApiRelust(ApiErrCodes.必传参数为空, $"参数名[{nameof(orgCode)}]"));
14 
15                 if (string.IsNullOrEmpty(userName))
16                     return Json(new BaseApiRelust(ApiErrCodes.必传参数为空, $"参数名[{nameof(userName)}]"));
17 
18                 if (string.IsNullOrEmpty(password))
19                     return Json(new BaseApiRelust(ApiErrCodes.必传参数为空, $"参数名[{nameof(password)}]"));
20 
21                 string key = "test2017";
22                 //解密
23                 string pwd = EncryUtils.DESDecrypt(password, key);
24 
25                 ResponseBase<bool> funRes = InvoiceProcessor.ValidateOrgUser(orgCode, userName, pwd);
26                 if (funRes.Error != null)
27                 {
28                     return Json(new BaseApiRelust(ApiErrCodes.数据处理异常, funRes.Msg));
29                 }
30                 if (!funRes.Result)
31                 {
32                     return Json(new BaseApiRelust(ApiErrCodes.数据处理失败, funRes.Msg));
33                 }
34                 /*先搜索当前业务的Token缓存集合是否有对应的Token,
35                  如果有先清除再新增,这里将加密后的pwd存储到服务器,将他设为token*/
36                 SiUser user = (SiUser)funRes.OutData;
37                 WebApiToken token = CreateToken(user.Id, user.Name, user.PassWord, WebApiBusType.InvoiceWeixin);
38                 InvoiceProcessor.AddWebApiToken(user.Id, token);
39                 return Json(new BaseApiRelust(ApiErrCodes.成功, token.Token));
40             }
41             catch (Exception ex)
42             {
43                 return Json(new BaseApiRelust(ApiErrCodes.数据处理异常, ex.Message));
44             }
45         }

下面是WebApiToken的对象

 1  /// <summary>
 2     /// webapi授权token
 3     /// </summary>
 4     public class WebApiToken 
 5     {
 6         /// <summary>
 7         /// 用户id
 8         /// </summary>
 9         public Guid UserId { get; set; }
10 
11         /// <summary>
12         /// 用户姓名
13         /// </summary>
14         public string UserName { get; set; }
15 
16         /// <summary>
17         /// 业务类型
18         /// </summary>
19         public WebApiBusType Type { get; set; }
20 
21         /// <summary>
22         /// Token值
23         /// </summary>
24         public string Token { get; set; }   
25     }

下面是创建token的方法

       /// <summary>
        /// 生成Token
        /// </summary>
        /// <typeparam name="T">账户id类型</typeparam>
        /// <param name="id">账户id</param>
        /// <param name="name">账户名</param>
        /// <param name="pwd">密码</param>
        /// <param name="busType">webapi业务类型</param>
        /// <returns></returns>
        public WebApiToken CreateToken(Guid id, string name, string pwd, WebApiBusType busType)
        {
            string note = $"id={id}&name={name}&pwd={pwd}&busType={busType.ToString()}";
            string key = RandomHelper.CreateRandomCode(8);
            string encry = EncryUtils.DESEncrypt(note, key);
            WebApiToken token = new WebApiToken
            {
                Token = encry,
                Type = busType,
                UserId = id,
                UserName = name                
            };
            return token;
        }

 

这里创建Token我传了一个WebApiBusType的枚举,这个枚举用来区分webapi的模块,用于进一步的权限控制;

接下来,是更新db的用户token以及缓存的token

 1         /// <summary>
 2         /// 新增webapi的授权token
 3         /// </summary>
 4         /// <param name="userId">用户id</param>
 5         /// <param name="token">token值</param>
 6         public static void AddWebApiToken(Guid userId, WebApiToken token)
 7         {
 8             string key = CacheKeys.WebApiToken.ToString();
 9             //移除该用户过期Token
10             List<WebApiToken> AuthentTokens = Air.Infrastructure.Cache.CacheManager.Current.Get<List<WebApiToken>>(key);
11             //根据用户id在数据库中获取Token ,然后再拼接成对应的缓存值到缓存集合中检索是否存在
12             SiUser user = SiUserManager.Current.FindByIdAsync(userId).Result;
13             WebApiToken oldValue = new WebApiToken
14             {
15                 UserId = user.Id,
16                 Token = user.Token,
17                 Type = WebApiBusType.InvoiceWeixin
18             };
19             if (AuthentTokens != null)
20                 AuthentTokens.Remove(oldValue);
21             else
22                 AuthentTokens = new List<WebApiToken>();
23             AuthentTokens.Add(token);
24             Infrastructure.Cache.CacheManager.Current.Remove<List<string>>(key);
25             Infrastructure.Cache.CacheManager.Current.AddOrUpdate(key, AuthentTokens);
26             //更新数据库Token值
27             user.Token = token.Token;
28             SiUserManager.Current.UpdateWebApiToken(user);
29         }

这里可以看出来Token是放到了缓存,从缓存获取到token后我们就可以知道是哪个用户在调用,根据WebApiBusType我们可以新增一个刷选器限制访问级别;

接下来看认证刷选器是如何实现的:

  1 using Air.Data.Enums;
  2 using Air.Data.Transfer;
  3 using System;
  4 using System.Collections.Generic;
  5 using System.Linq;
  6 using System.Threading.Tasks;
  7 using System.Web.Http;
  8 using System.Web.Http.Controllers;
  9 using System.Web.Http.Filters;
 10 using System.Net.Http;
 11 using Newtonsoft.Json;
 12 using System.Text;
 13 using Newtonsoft.Json.Linq;
 14 
 15 namespace Air.Api.Filters
 16 {
 17     /// <summary>                    
 18     /// 认证刷选器 :System.Web.Http.AuthorizeAttribute  System.Web.Http.AuthorizeAttribute// AuthorizationFilterAttribute
 19     /// :ActionFilterAttribute
 20     /// </summary>
 21     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
 22     public class WebApiAuthenticationAttribute : AuthorizationFilterAttribute 
 23     {
 24         const string TOKEN_NAME = "X-Token";
 25         /// <summary>
 26         /// 重写请求授权
 27         /// </summary>
 28         /// <param name="actionContext"></param>
 29         public override void OnAuthorization(HttpActionContext actionContext)
 30         {
 31             if (!IsAnonymous(actionContext))
 32             {
 33                 HttpContent content = actionContext.Request.Content;
 34                 string encryptedToken = actionContext.Request.Headers.GetValues(TOKEN_NAME).First();
 35                 ResponseBase<bool> response = ValidateToken(encryptedToken);
 36                 if (!response.Result)
 37                 {
 38                     SetUnauthorizedResponse(actionContext);
 39                     WebApiToken token = (WebApiToken)response.OutData;
 40                     return;
 41                 }
 42                 //是否有此action权限
 43                 if (!ValidateAuth(actionContext, encryptedToken))
 44                 {
 45                     SetUnauthorizedResponse(actionContext);
 46                     return;
 47                 }
 48             }
 49         }
 50         private void SetUnauthorizedResponse(HttpActionContext actionContext)
 51         {
 52             var response = actionContext.Response = actionContext.Response ?? new HttpResponseMessage();      
 53             var content = new  BaseApiRelust(ApiErrCodes.未授权, "当前访问身份无效!");
 54             response.Content = new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json");
 55         }
 56            
 57  
 58         /// <summary>
 59         /// 是否跳过授权验证
 60         /// </summary>
 61         /// <param name="actionContext"></param>
 62         /// <returns></returns>
 63         private bool IsAnonymous(HttpActionContext actionContext)
 64         {
 65             if (actionContext.ActionDescriptor.GetCustomAttributes<NoAuthenticationAttribute>().Any()||
 66                 actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
 67                 return true;
 68             return false;
 69         }
 70         /// <summary>
 71         /// 验证Token是否存在
 72         /// </summary>
 73         /// <param name="value"></param>
 74         /// <returns></returns>
 75         private ResponseBase<bool> ValidateToken(string value)
 76         {
 77             /*从缓存中获取*/
 78             ResponseBase<bool> responese = new ResponseBase<bool>();
 79             string key = Air.Data.Enums.CacheKeys.WebApiToken.ToString();
 80             List<WebApiToken> tokenLst = Air.Infrastructure.Cache.CacheManager.Current.Get<List<WebApiToken>>(key);
 81             WebApiToken token = tokenLst?.SingleOrDefault(p => p.Token == value);
 82             responese.Result = token == null ? false : true;
 83             responese.OutData = token;
 84             return responese;
 85         }
 86 
 87         /// <summary>
 88         /// 验证是否有权限访问Action
 89         /// </summary>
 90         /// <param name="actionContext"></param>
 91         /// <param name="value"></param>
 92         /// <returns></returns>
 93         private bool ValidateAuth(HttpActionContext actionContext, string value)
 94         {
 95             string key = Air.Data.Enums.CacheKeys.WebApiToken.ToString();
 96             List<WebApiToken> tokenLst = Air.Infrastructure.Cache.CacheManager.Current.Get<List<WebApiToken>>(key);
 97             WebApiToken token = tokenLst?.FirstOrDefault(p => p.Token == value);
 98             if (token == null) return false;
 99 
100             var moduleAuthorizationAction = actionContext.ActionDescriptor.GetCustomAttributes<ModuleAuthorizationAttribute>();
101             if (moduleAuthorizationAction.Any())
102             {
103                 WebApiBusType busType = token.Type;
104                 if (!moduleAuthorizationAction[0].Authorizations.Contains(busType))
105                     return false;
106             }
107             return true;
108         }
109 
110     }
111 }

 

还有一个特性就是ModuleAuthorizationAttribute,这个特性就是注明webapi能够给哪几个WebApiBusType枚举值调用,代码如下:

 1     /// <summary>
 2     /// 权限控制标记
 3     /// </summary>
 4     [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
 5     public class ModuleAuthorizationAttribute : Attribute
 6     {
 7         /// <summary>
 8         /// 权限控制构造函数
 9         /// </summary>
10         /// <param name="authorization">允许操作的模块</param>
11         public ModuleAuthorizationAttribute(params WebApiBusType[] authorization)
12         {
13             this.Authorizations = authorization;
14         }
15 
16         /// <summary>
17         /// 允许访问的模块
18         /// </summary>
19         public WebApiBusType[] Authorizations { get; set; }
20     }
View Code

好了,整个完成了。客户端试下就是这结果:

 

posted @ 2017-08-24 20:28  长沙大鹏  阅读(629)  评论(0编辑  收藏  举报