• 基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现


    概述: 

      ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作。但是在使用API的时候总会遇到跨域请求的问题, 特别各种APP万花齐放的今天,对API使用者身份角色验证是不能避免的(完全开发的API不需要对使用者身份角色进行管控,可以绕过),这篇文章就来谈谈基于令牌TOKEN身份验证的实现。

    问题:

       对于Web API的选择性的开放,使用者无论使用AJAX,还是HttpClient对接,总要对使用者的身份角色进行验证,然而使用API总有跨域使用情况的存在,这样就导致所有基于cookie验证方式都不再适用于API的验证。

    原因:

      比如,基于form表单验证的基础是登录验证成功后,用户的信息存在缓存或数据库或cookie,无论哪种方式存储用户信息,都不能绕过对cookie的使用,所以form表单验证方法对于禁用cookie的浏览器都不能正常使用,结论就是不能使用cookie 的环境就不能使用基本的form表单验证方式。因此WEB API 由于跨域的使用,导致cookie不能正常工作,所以不能再使用基于表单验证的方式来实现。

    基于令牌TOKEN验证方法的实现:

    方法一:

         1. 实现对缓存TOKEN的管理,以防IIS服务器的宕机,可以对TOKEN进行持久化存储处理,每次IIS重启重新初始化已经登录成功TOKEN缓存。实现如下:

    复制代码
      1 public class UserTokenManager
      2     {
      3         private static readonly IUserTokenRepository _tokenRep;
      4         private const string TOKENNAME = "PASSPORT.TOKEN";
      5 
      6         static UserTokenManager()
      7         {
      8             _tokenRep = ContainerManager.Resolve<IUserTokenRepository>();
      9         }
     10         /// <summary>
     11         /// 初始化缓存
     12         /// </summary>
     13         private static List<UserToken> InitCache()
     14         {
     15             if (HttpRuntime.Cache[TOKENNAME] == null)
     16             {
     17                 var tokens = _tokenRep.GetAll();
     18                 // cache 的过期时间, 令牌过期时间 *2
     19                 HttpRuntime.Cache.Insert(TOKENNAME, tokens, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromDays(7 * 2));
     20             }
     21             var ts = (List<UserToken>)HttpRuntime.Cache[TOKENNAME];
     22             return ts;
     23         }
     24 
     25 
     26         public static int GetUId(string token)
     27         {
     28             var tokens = InitCache();
     29             var result = 0;
     30             if (tokens.Count > 0)
     31             {
     32                 var id = tokens.Where(c => c.Token == token).Select(c => c.UId).FirstOrDefault();
     33                 if (id != null)
     34                     result = id.Value;
     35             }
     36             return result;
     37         }
     38 
     39 
     40         public static string GetPermission(string token)
     41         {
     42             var tokens = InitCache();
     43             if (tokens.Count == 0)
     44                 return "NoAuthorize";
     45             else
     46                 return tokens.Where(c => c.Token == token).Select(c => c.Permission).FirstOrDefault();
     47         }
     48 
     49         public static string GetUserType(string token)
     50         {
     51             var tokens = InitCache();
     52             if (tokens.Count == 0)
     53                 return "";
     54             else
     55                 return tokens.Where(c => c.Token == token).Select(c => c.UserType).FirstOrDefault();
     56         }
     57 
     58         /// <summary>
     59         /// 判断令牌是否存在
     60         /// </summary>
     61         /// <param name="token"></param>
     62         /// <returns></returns>
     63         public static bool IsExistToken(string token)
     64         {
     65             var tokens = InitCache();
     66             if (tokens.Count == 0) return false;
     67             else
     68             {
     69                 var t = tokens.Where(c => c.Token == token).FirstOrDefault();
     70                 if (t == null)
     71                     return false;
     72                 else if (t.Timeout < DateTime.Now)
     73                 {
     74                     RemoveToken(t);
     75                     return false;
     76                 }
     77                 else
     78                 {
     79                     // 小于8小时 更新过期时间
     80                     if ((t.Timeout - DateTime.Now).TotalMinutes < 1 * 60 - 1)
     81                     {
     82                         t.Timeout = DateTime.Now.AddHours(8);
     83                         UpdateToken(t);
     84                     }
     85                     return true;
     86                 }
     87 
     88             }
     89         }
     90 
     91         /// <summary>
     92         /// 添加令牌, 没有则添加,有则更新
     93         /// </summary>
     94         /// <param name="token"></param>
     95         public static void AddToken(UserToken token)
     96         {
     97             var tokens = InitCache();
     98             // 不存在  怎增加
     99             if (!IsExistToken(token.Token))
    100             {
    101                 token.ID = 0;
    102                 tokens.Add(token);
    103                 // 插入数据库
    104                 _tokenRep.Add(token);
    105             }
    106             else  // 有则更新
    107             {
    108                 UpdateToken(token);
    109             }
    110         }
    111 
    112         public static bool UpdateToken(UserToken token)
    113         {
    114             var tokens = InitCache();
    115             if (tokens.Count == 0) return false;
    116             else
    117             {
    118                 var t = tokens.Where(c => c.Token == token.Token).FirstOrDefault();
    119                 if (t == null)
    120                     return false;
    121                 t.Timeout = token.Timeout;
    122                 // 更新数据库
    123                 var tt = _tokenRep.FindByToken(token.Token);
    124                 if (tt != null)
    125                 {
    126                     tt.UserType = token.UserType;
    127                     tt.UId = token.UId;
    128                     tt.Permission = token.Permission;
    129                     tt.Timeout = token.Timeout;
    130                     _tokenRep.Update(tt);
    131                 }
    132                 return true;
    133             }
    134         }
    135         /// <summary>
    136         /// 移除指定令牌
    137         /// </summary>
    138         /// <param name="token"></param>
    139         /// <returns></returns>
    140         public static void RemoveToken(UserToken token)
    141         {
    142             var tokens = InitCache();
    143             if (tokens.Count == 0) return;
    144             tokens.Remove(token);
    145             _tokenRep.Remove(token);
    146         }
    147 
    148         public static void RemoveToken(string token)
    149         {
    150             var tokens = InitCache();
    151             if (tokens.Count == 0) return;
    152 
    153             var ts = tokens.Where(c => c.Token == token).ToList();
    154             foreach (var t in ts)
    155             {
    156                 tokens.Remove(t);
    157                 var tt = _tokenRep.FindByToken(t.Token);
    158                 if (tt != null)
    159                     _tokenRep.Remove(tt);
    160             }
    161         }
    162 
    163 
    164         public static void RemoveToken(int uid)
    165         {
    166             var tokens = InitCache();
    167             if (tokens.Count == 0) return;
    168 
    169             var ts = tokens.Where(c => c.UId == uid).ToList();
    170             foreach (var t in ts)
    171             {
    172                 tokens.Remove(t);
    173                 var tt = _tokenRep.FindByToken(t.Token);
    174                 if (tt != null)
    175                     _tokenRep.Remove(tt);
    176             }
    177         }
    178     }
    复制代码

         2. 新建ApiAuthorizeAttribute类,继承AuthorizeAttribute,重写方法IsAuthorized,这样基于TOKEN验证方式就完成了。实现如下:

    复制代码
     1   public class ApiAuthorizeAttribute : AuthorizeAttribute
     2     {
     3         protected override bool IsAuthorized(HttpActionContext actionContext)
     4         {
     5             // 验证token
     6             //var token = actionContext.Request.Headers.Authorization;
     7             var ts = actionContext.Request.Headers.Where(c => c.Key.ToLower() == "token").FirstOrDefault().Value;
     8             if (ts != null && ts.Count() > 0)
     9             {
    10                 var token = ts.First<string>();
    11                 // 验证token
    12                 if (!UserTokenManager.IsExistToken(token))
    13                 {
    14                     return false;
    15                 }
    16                 return true;
    17             }
    18 
    19             if (actionContext.Request.Method == HttpMethod.Options)
    20                 return true;
    21             return false;
    22         }
    23     }
    复制代码

         3. 登录实现

    复制代码
      1     /// <summary>
      2     /// 账户
      3     /// </summary>
      4     public class AccountController : ApiController
      5     {
      6         /// <summary>
      7         /// 登录
      8         /// </summary>
      9         /// <param name="user">登录人员信息: 账号,密码 ,是否记住密码</param>
     10         /// <returns></returns>
     11         [HttpPost]
     12         [AllowAnonymous]
     13         public ResultData Login([FromBody]LoginUser user)
     14         {
     15             string mobile = user.Mobile;
     16             string password = user.Password;
     17             bool IsRememberMe = user.IsRememberMe;
     18 
     19             if (string.IsNullOrEmpty(mobile) || string.IsNullOrEmpty(password))
     20                 return new ResultData(((int)LoginResultEnum.UserNameOrPasswordError), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameOrPasswordError));
     21 
     22             User u=null;
     23             IMembershipService membershipSvc = ContainerManager.Container.Resolve<IMembershipService>();
     24             LoginResultEnum loginResult = membershipSvc.Login(mobile, password, out u);
     25             if (loginResult == LoginResultEnum.Success)
     26             {
     27                 //SetAuthenticationTicket(u, IsRememberMe);
     28 
     29                 // token   处理
     30                 UserTokenManager.RemoveToken(u.ID);
     31                 // 生成新Token
     32                 var token = Utility.MD5Encrypt(string.Format("{0}{1}", Guid.NewGuid().ToString("D"), DateTime.Now.Ticks));
     33                 // token过期时间
     34                 int timeout = 8;
     35                 if (!int.TryParse(ConfigurationManager.AppSettings["TokenTimeout"], out timeout))
     36                     timeout = 8;
     37                 // 创建新token
     38                 var ut = new UserToken()
     39                 {
     40                     Token = token,
     41                     Timeout = DateTime.Now.AddHours(timeout),
     42                     UId = u.ID,
     43                     UserType = (u.IsSaler.HasValue && u.IsSaler.Value) ? "Saler" : "Vip"
     44                 };
     45 
     46                 UserTokenManager.AddToken(ut);
     47 
     48 
     49                 // 登录log
     50                 var logRep = ContainerManager.Container.Resolve<ISysLogRepository>();
     51                 var log = new Log()
     52                 {
     53                     Action = "Login",
     54                     Detail = "会员登录:" + u.Mobile + "|" + u.Name,
     55                     CreateDate = DateTime.Now,
     56                     CreatorLoginName = u.Mobile,
     57                     IpAddress = GetClientIp(this.Request)
     58                 };
     59 
     60                 logRep.Add(log);
     61 
     62                 var data = new
     63                 {
     64                     id = u.ID,
     65                     issaler = u.IsSaler.HasValue ? u.IsSaler.Value : false,
     66                     mobile = u.Mobile,
     67                     token = token
     68                 };
     69                 var result = new ResultData(data);
     70                 result.desc = "登录成功";
     71                 return result;
     72             }
     73 
     74             if (loginResult == LoginResultEnum.UserNameUnExists)
     75             {
     76                 return new ResultData(((int)LoginResultEnum.UserNameUnExists), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameUnExists));
     77             }
     78             if (loginResult == LoginResultEnum.VerifyCodeError)
     79             {
     80                 return new ResultData(((int)LoginResultEnum.VerifyCodeError), EnumExtension.GetEnumDescription(LoginResultEnum.VerifyCodeError));
     81             }
     82             if (loginResult == LoginResultEnum.UserNameOrPasswordError)
     83             {
     84                 return new ResultData(((int)LoginResultEnum.UserNameOrPasswordError), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameOrPasswordError));
     85             }
     86             return new ResultData(ResultType.UnknowError, "登录失败,原因未知");
     87         }
     88         /// <summary>
     89         /// 退出当前账号
     90         /// </summary>
     91         /// <returns></returns>
     92         [HttpPost]
     93         public ResultData SignOut()
     94         {
     95             // 登录log
     96             var logRep = ContainerManager.Resolve<ISysLogRepository>();
     97             var log = new Log()
     98             {
     99                 Action = "SignOut",
    100                 Detail = "会员退出:" + RISContext.Current.CurrentUserInfo.UserName,
    101                 CreateDate = DateTime.Now,
    102                 CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName,
    103                 IpAddress = GetClientIp(this.Request)
    104             };
    105             logRep.Add(log);
    106             //System.Web.Security.FormsAuthentication.SignOut();
    107             UserTokenManager.RemoveToken(this.Token);
    108             return new ResultData(ResultType.Success, "退出成功");
    109         }
    110     }
    复制代码

        4. 测试API

             这样就可以配合.NET原有的 AllowAnonymousAttribute 属性使用, 使用方法如下:
             不需要验证身份的 类或者Action 添加  [AllowAnonymous]属性,否则添加[ApiAuthorize]

    复制代码
     1     /// <summary>
     2     /// 测试
     3     /// </summary>
     4     [ApiAuthorize]
     5     public class TestController : BaseApiController
     6     {
     7         /// <summary>
     8         /// 测试权限1
     9         /// </summary>
    10         [HttpGet]
    11         public string TestAuthorize1()
    12         {
    13             return "TestAuthorize1";
    14         }
    15         /// <summary>
    16         /// 测试权限2
    17         /// </summary>
    18         [AllowAnonymous]
    19         [HttpGet]
    20         public string TestAuthorize2()
    21         {
    22             return "TestAuthorize2";
    23         }
    24     }
    复制代码

    测试一:

    复制代码
     1 //TestAuthorize
     2 function TestAuthorize1() {
     3     $.ajax({
     4         type: "get",
     5         url: host + "/mobileapi/test/TestAuthorize1",
     6         dataType: "text",
     7         data: {},
     8         beforeSend: function (request) {
     9             request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
    10         },
    11         success: function (data) {
    12             alert(data);
    13         },
    14         error: function (x, y, z) {
    15             alert("报错无语");
    16         }
    17     });
    18 }
    复制代码

         结果如下:

    测试二:

    复制代码
     1 //TestAuthorize
     2 function TestAuthorize2() {
     3     $.ajax({
     4         type: "get",
     5         url: host + "/mobileapi/test/TestAuthorize2",
     6         dataType: "text",
     7         data: {},
     8         beforeSend: function (request) {
     9             request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
    10         },
    11         success: function (data) {
    12             alert(data);
    13         },
    14         error: function (x, y, z) {
    15             alert("报错无语");
    16         }
    17     });
    18 }
    复制代码

        结果如下:

    测试三:

    复制代码
     1 //TestAuthorize
     2 function TestAuthorize1() {
     3     $.ajax({
     4         type: "get",
     5         url: host + "/mobileapi/test/TestAuthorize1",
     6         dataType: "text",
     7         data: {},
     8         beforeSend: function (request) {
     9             //request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
    10         },
    11         success: function (data) {
    12             alert(data);
    13         },
    14         error: function (x, y, z) {
    15             alert("报错无语");
    16         }
    17     });
    18 }
    复制代码

         结果如下:

    测试四:

    复制代码
     1 //TestAuthorize
     2 function TestAuthorize2() {
     3     $.ajax({
     4         type: "get",
     5         url: host + "/mobileapi/test/TestAuthorize2",
     6         dataType: "text",
     7         data: {},
     8         beforeSend: function (request) {
     9             //request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
    10         },
    11         success: function (data) {
    12             alert(data);
    13         },
    14         error: function (x, y, z) {
    15             alert("报错无语");
    16         }
    17     });
    18 }
    复制代码

        结果如下:


    方法二:

       此方法缺点就是每次请求都需要附带token请求参数,这对于有强迫症的程序猿来说是一种折磨,不细说,实现代码如下,有需要的自己研究研究:

    复制代码
     1     /// <summary>
     2     /// 用户令牌验证
     3     /// </summary>
     4     public class TokenAuthorizeAttribute : ActionFilterAttribute
     5     {
     6         private const string UserToken = "token";
     7         public override void OnActionExecuting(HttpActionContext actionContext)
     8         {
     9             // 匿名访问验证
    10             var anonymousAction = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>();
    11             if (!anonymousAction.Any())
    12             {
    13                 // 验证token
    14                 var token = TokenVerification(actionContext);
    15             }
    16             base.OnActionExecuting(actionContext);
    17         }
    18 
    19         /// <summary>
    20         /// 身份令牌验证
    21         /// </summary>
    22         /// <param name="actionContext"></param>
    23         protected virtual string TokenVerification(HttpActionContext actionContext)
    24         {
    25             string msg = "";
    26             // 获取token
    27             var token = GetToken(actionContext, out msg);
    28             if (!string.IsNullOrEmpty(msg))
    29                 actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.OK, new NoAuthData() { code = "401", msg = msg });
    30             // 判断token是否有效
    31             if (!UserTokenManager.IsExistToken(token))
    32             {
    33                 actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.OK, new NoAuthData() { code = "401", msg = "Token已失效,请重新登录!" });
    34                 //actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.Unauthorized, new NoAuthData() { code = "401", msg = "Token已失效,请重新登录!" });
    35                 // actionContext.Response = actionContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, "Token已失效,请重新登录!");
    36             }
    37 
    38             return token;
    39         }
    40 
    41         private string GetToken(HttpActionContext actionContext, out string msg)
    42         {
    43             Dictionary<string, object> actionArguments = actionContext.ActionArguments;
    44             HttpMethod type = actionContext.Request.Method;
    45             msg = "";
    46             var token = "";
    47             if (type == HttpMethod.Post)
    48             {
    49                 if (actionArguments.ContainsKey(UserToken))
    50                 {
    51                     if (actionArguments[UserToken] != null)
    52                         token = actionArguments[UserToken].ToString();
    53                 }
    54                 else
    55                 {
    56                     foreach (var value in actionArguments.Values)
    57                     {
    58                         if (value != null && value.GetType().GetProperty(UserToken) != null)
    59                             token = value.GetType().GetProperty(UserToken).GetValue(value, null).ToString();
    60                     }
    61                 }
    62 
    63                 if (string.IsNullOrEmpty(token))
    64                     msg = "登录超时,请重新登录!";
    65             }
    66             else if (type == HttpMethod.Get)
    67             {
    68                 if (!actionArguments.ContainsKey(UserToken))
    69                     msg = "还未登录";
    70                 // throw new HttpException(401, "还未登录");
    71 
    72                 if (actionArguments[UserToken] != null)
    73                     token = actionArguments[UserToken].ToString();
    74                 else
    75                     msg = "登录超时,请重新登录!";
    76             }
    77             else
    78             {
    79                 throw new HttpException(404, "暂未开放除POST,GET之外的访问方式!");
    80             }
    81             return token;
    82         }
    83     }
    84 
    85     public class NoAuthData
    86     {
    87         public string code { get; set; }
    88         public string msg { get; set; }
    89     }
    复制代码


    此篇到此结束,欢迎大家讨论!

      

    那些曾以为念念不忘的事情就在我们念念不忘的过程中,被我们遗忘了。
     
  • 相关阅读:
    【转】浅谈MVC与三层架构
    【转】小结登录的几种交互方式
    【转】 App架构设计经验谈:接口的设计
    【转】JS编码解码、C#编码解码
    jQuery 判断是否包含某个属性
    jQuery on()方法
    常用正则表达式大全
    Fiddler 抓取手机APP数据包
    [Asp.net]通过uploadify将文件上传到B服务器的共享文件夹中
    ★电车难题的n个坑爹变种
  • 原文地址:https://www.cnblogs.com/webenh/p/6107311.html
Copyright © 2020-2023  润新知