首页 > 代码库 > 基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现

基于.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             else117             {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                 // 验证token12                 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 = http://www.mamicode.com/new"登录成功"; 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         /// 测试权限217         /// </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());  // 请求发起前在头部附加token10         },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());  // 请求发起前在头部附加token10         },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());  // 请求发起前在头部附加token10         },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());  // 请求发起前在头部附加token10         },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                 // 验证token14                 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             // 获取token27             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                 else55                 {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                 else75                     msg = "登录超时,请重新登录!";76             }77             else78             {79                 throw new HttpException(404, "暂未开放除POST,GET之外的访问方式!");80             }81             return token;82         }83     }84 85     public class NoAuthData86     {87         public string code { get; set; }88         public string msg { get; set; }89     }

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