Asp.Net WebApi一个简单的Token验证

一、前言

WebAPI主要开放数据给手机APP,Pad,其余须要得知数据的系统,或者软件应用。Web 用户的身份验证,及页面操做权限验证是B/S系统的基础功能。我上次写的《Asp.Net MVC WebAPI的建立与前台Jquery ajax后台HttpClient调用详解》这种跟明显安全性不是那么好,因而乎这个就来了 ,用户须要访问的API都必须带有票据过来,说白了就是登录以后含有用户信息的Token。开始撸...javascript

二、新建一个WebApi项目

在App_Start文件夹下面新建一个BaseApiController控制器,这是基础的Api控制器,后面有要验证的接口都继承这个控制器:html

using LoginReqToken.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; namespace LoginReqToken.App_Start { /// <summary>
    /// 基础Api控制器 全部的都继承他 /// </summary>
    public class BaseApiController : ApiController { /// <summary>
       /// 构造函数赋值 /// </summary>
        public BaseApiController() { TokenValue = HttpContext.Current.Session[LoginID] ?? ""; HttpContext.Current.Request.Headers.Add("TokenValue", TokenValue.ToString()); } /// <summary>
        /// 数据库上下文 /// </summary>
        public WYDBContext db = WYDBContextFactory.GetDbContext(); /// <summary>
        /// token值 登陆后赋值请求api的时候添加到header中 /// </summary>
        public static object TokenValue { get; set; } = ""; /// <summary>
        /// 登陆者帐号 /// </summary>
        public static string LoginID { get; set; } = ""; } }

这个构造函数里主动加一个header头信息 ,由于每次访问的时候都要执行构造函数,在那边验证的时候都要从Header中取出来,计算出用户名 是否跟Session缓存的一致这样判断的前端

三、在建一个TokenCheckFilter.cs

继承AuthorizeAttribute重写基类的验证方式,重写HandleUnauthorizedRequestjava

using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Web; using System.Web.Helpers; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Security; namespace LoginReqToken.App_Start { /// <summary>
    /// token验证 /// </summary>
    public class TokenCheckFilter: AuthorizeAttribute { /// <summary>
        /// 重写基类的验证方式,加入自定义的Ticket验证 /// </summary>
        /// <param name="actionContext"></param>
        public override void OnAuthorization(HttpActionContext actionContext) { var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase; //获取token(请求头里面的值)
            var token = HttpContext.Current.Request.Headers["TokenValue"] ?? ""; //是否为空
            if (!string.IsNullOrEmpty(token.ToString())) { //解密用户ticket,并校验用户名密码是否匹配
                if (ValidateTicket(token.ToString())) base.IsAuthorized(actionContext); else HandleUnauthorizedRequest(actionContext); } //若是取不到身份验证信息,而且不容许匿名访问,则返回未验证403
            else { var attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>(); bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute); if (isAnonymous) base.OnAuthorization(actionContext); else HandleUnauthorizedRequest(actionContext); } } //校验用户名密码(对Session匹配,或数据库数据匹配)
        private bool ValidateTicket(string encryptToken) { //解密Ticket
            var strTicket = FormsAuthentication.Decrypt(encryptToken).UserData; //从Ticket里面获取用户名和密码
            var index = strTicket.IndexOf("&"); string userName = strTicket.Substring(0, index); string password = strTicket.Substring(index + 1); //取得session,不经过说明用户退出,或者session已通过期
            var token = HttpContext.Current.Session[userName]; if (token == null) return false; //对比session中的令牌
            if (token.ToString() == encryptToken) return true; return false; } /// <summary>
        /// 重写HandleUnauthorizedRequest /// </summary>
        /// <param name="filterContext"></param>
        protected override void HandleUnauthorizedRequest(HttpActionContext filterContext) { base.HandleUnauthorizedRequest(filterContext); var response = filterContext.Response = filterContext.Response ?? new HttpResponseMessage(); //状态码401改成其余状态码来避免被重定向。最合理的是改成403,表示服务器拒绝。
            response.StatusCode = HttpStatusCode.Forbidden; var content = new { success = false, errs = new[] { "服务端拒绝访问:你没有权限?,或者掉线了?" } }; response.Content = new StringContent(Json.Encode(content), Encoding.UTF8, "application/json"); } } }

四、在WebApiConfig.cs配置文件里面修改一下路由加上/{action},这样就能调用到具体的哪个了

 

 

 

Webapi默认是不支持Session的,因此咱们须要在Global加载时候添加对Session的支持,在Global.asax里面重写Application_PostAuthorizeRequest,否则运行调用会直接异常jquery

public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } /// <summary>
        /// 重写Application_PostAuthorizeRequest /// </summary>
        protected void Application_PostAuthorizeRequest() { //对Session的支持,否则运行调用会直接异常
 HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required); } }

 

 

五、如今来写一个登录

新建一个控制器LoginController继承BaseApiController 里面写一个登录的方法Login 登录页面就直接在Home的index里面写一个简单的就好了这个控制器访问就不受限制了加上注解ajax

[AllowAnonymous] public class LoginController : BaseApiController { [HttpGet] public object Login(string uName, string uPassword) { var user = db.Users.Where(x => x.LoginID == uName && x.Password == uPassword).FirstOrDefault(); if (user==null) { return Json(new { ret = 0, data = "", msg = "用户名密码错误" }); } FormsAuthenticationTicket token = new FormsAuthenticationTicket(0, uName, DateTime.Now, DateTime.Now.AddHours(12), true, $"{uName}&{uPassword}", FormsAuthentication.FormsCookiePath); //返回登陆结果、用户信息、用户验证票据信息
            var _token = FormsAuthentication.Encrypt(token); //将身份信息保存在session中,验证当前请求是不是有效请求
            LoginID = uName; TokenValue = _token; HttpContext.Current.Session[LoginID] = _token; return Json(new { ret = 1, data = _token, msg = "登陆成功!" }); } }

 

登录页面 简单而粗暴数据库

 

<br /><br />
<input type="text" name="txtLoginID" id="txtLoginID"  />
<br /><br />
<input type="password" name="txtPassword" id="txtPassword"  />
<br /><br />
<input type="button" id="btnSave" value="登陆验证" />
<script type="text/javascript" src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript"> $(document).ready(function () { $("#btnSave").click(function () { $.ajax({ type: "GET", url: "/Api/Login/Login", dataType: "json", data: { "uName": $("#txtLoginID").val(), "uPassword": $("#txtPassword").val()}, success: function (data) { if (data.ret > 0) { alert(data.msg+"Token: "+data.data); } else { alert(data.msg); } }, error: function (ret) { console.log(ret); } }); }); }); </script>

 

 

 

 登录这个我是写了连接数据库的本身练习能够最易更改一个固定的值 如今应该能够看到返回的Token数据了json

 

 

 

六、如今就能够写Api

都继承BaseApiController这个控制器的方法上面须要验证的都要加上验证的注解,我是整个控制都要就直接写在类上面了,随便写一个举举例子,就好比全国省市县的查询api

using LoginReqToken.App_Start; using LoginReqToken.Models; using LoginReqToken.Models.DTO; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace LoginReqToken.Controllers { /// <summary>
    /// 区域查询 /// </summary>
 [TokenCheckFilter] public class AreaOpController : BaseApiController { /// <summary>
        /// 获取所有区域 /// </summary>
        /// <returns></returns>
        public Result GetAllAreas() { var data = db.AddressAll.OrderBy(x => x.ID); if(data.Count()>0) { var ret = new Result() { Ret = 1, Code = "200", Msg = "获取数据成功", Data = JsonConvert.SerializeObject(data) }; return ret; } else { var ret = new Result() { Ret = 0, Code = "400", Msg = "接口失败异常", Data = "" }; return ret; } } /// <summary>
        /// 查询某个省市直辖市自治区下全部的信息 /// </summary>
        /// <param name="name">省名称(全名)</param>
        /// <returns></returns>
        public Result GetProvinceByName(string name) { var codeID = db.AddressAll.FirstOrDefault(x => x.Name == name)?.ID; if(codeID<=0) { var ret = new Result() { Ret = 1, Code = "F", Msg = "没有查到相关数据", Data = "" }; return ret; } var bb = db.AddressAll.Where(x=>x.ID>0).AsEnumerable(); var data = GetProvinceCity(bb,codeID).ToList(); if (data.Count() > 0) { var ret = new Result() { Ret = 1, Code = "200", Msg = "获取数据成功", Data = JsonConvert.SerializeObject(data) }; return ret; } else { var ret = new Result() { Ret = 0, Code = "500", Msg = "查询不到数据或者接口调用出错", Data = "" }; return ret; } } /// <summary>
        /// 递归获取树形数据 /// </summary>
        /// <param name="areasDTOs"></param>
        /// <param name="parentID"></param>
        /// <returns></returns>
        public IEnumerable<object> GetProvinceCity(IEnumerable<AddressAll> areasDTOs,int? parentID) { var data = areasDTOs as AddressAll[] ?? areasDTOs.ToArray(); var ret = data.Where(n => n.ParentID == parentID).Select(n => new { n.ID, n.Code, n.ParentID, n.Name, n.LevelNum, n.OrderNum, children = GetProvinceCity(data, n.ID) }); return ret; } } }

 

记录一个EF随意取数据库条数信息是这么写的 var data = db.CnblogsList.OrderBy(p => Guid.NewGuid()).Take(100);浏览器

如今看效果图

 

 

 没有登录的时候是进不去的 postman上面的效果也看一下

 

 

效果都是同样的,若是登陆了就能够直接访问 了 不用加参数 ,只有方法须要参数的就能够加 

 

 

 

 

 

 这里贴一个调用的代码:

HttpClient bb = new HttpClient(); //获取端口
            HttpContent httpContent = new StringContent(""); httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); var dl = bb.GetAsync("http://localhost:63828/api/Login/login?uName=admin&uPassword=admin888").Result.Content.ReadAsStringAsync().Result; var token = JsonConvert.DeserializeObject<Result>(dl); for (var i=0;i<100;i++) { var ret = bb.GetAsync("http://localhost:63828/api/Cnblog/GetAllArtic").Result.Content.ReadAsStringAsync().Result; }

 

七、总结

1)、整体思路,若是是合法的Http请求,在Http请求头中会有用户身份的票据信息,服务端会读取票据信息,并校验票据信息是否完整有效,若是知足校验要求,则进行业务数据的处理,并返回给请求发起方;

2) 若是没有票据信息,或者票据信息不是合法的,则返回“未受权的访问”异常消息给前端,由前端处理此异常。

3)、登陆的时候判断用户名跟密码对不对,对了就生成用户信息的Token,Session保存一个Token,BaseApiController里面的登陆名跟Token也赋值了。保存这些票据信息。

4)、当用户有权限操做页面或页面元素时,跳转到页面,并由页面Controller提交业务数据处理请求到api服务器; 若是用户没有权限访问该页面或页面元素时,则显示“未受权的访问操做”,跳转到系统异常处理页面。

5)、 api业务服务处理业务逻辑,并将结果以Json 数据返回,返回渲染后的页面给浏览器前端,并呈现业务数据到页面;

八、测试地址

http://www.yijianlan.com:8001/   ---------------------->先登陆,用户名 test密码 123456 能够调用调试的接口 而后访问看看,其余的js 调用, 其余平台的我没有试过,还不知道问题,欢迎指教!

http://www.yijianlan.com:8001/api/AreaOp/GetProvinceByName?name=省全称   -------->    查看某个省市的全部子集

http://www.yijianlan.com:8001/api/AreaOp/GetAllAreas -------------------------------------------->  获取所有区域(全国首位省市县)

http://www.yijianlan.com:8001/api/Cnblog/GetAllArtic -----------------------------------------------> 获取博客园数据(这是之前爬虫抓的有2年了吧),随机一百条

http://www.yijianlan.com:8001/api/Cnblog/GetArticByName?name=标题 --------------------->  查询数据按标题

 

 

 

原文出处:https://www.cnblogs.com/w5942066/p/12055542.html

相关文章
相关标签/搜索