Net WebApi一个简单的Token验证

1、前言

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

2、新建一个WebApi项目

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

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缓存的一致这样判断的

3、在建一个TokenCheckFilter.cs

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

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");
        }

    }
}

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

Webapi默认是不支持Session的,所以我们需要在Global加载时候添加对Session的支持,在Global.asax里面重写Application_PostAuthorizeRequest,不然运行调用会直接异常

    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);
        }
    }

5、现在来写一个登陆

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

[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数据了

6、现在就可以写Api

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

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;
            }

7、总结

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

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

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

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

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

8、测试地址

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/Leo_wl/p/12324741.html

时间: 2024-10-02 19:53:09

Net WebApi一个简单的Token验证的相关文章

一个简单的通用验证类的实现

进园子三年多了,从来都只是看各位大神的文章,而没有写过,今天心血来潮来写一篇,本人水平略浅,写得不好勿见怪,大神勿喷 首先定义一个验证类型枚举,这里只列出最简单的两种验证类型 1 /// <summary> 2 /// 验证类型枚举 3 /// </summary> 4 public enum ValidateType 5 { 6 /// <summary> 7 /// 不为NULL验证 8 /// </summary> 9 NotNullValidate,

业务重点-实现一个简单的手机号码验证

前言 ????本文纯干货,直接拿走使用,不用付费.在业务开发中,手机号码验证是我们常常需要面对的问题,目前市场上各种各样的手机号码验证方式,比如正则表达式等等,本文结合实际业务场景,在业务级别对手机号码进行严格验证:同时增加可配置方式,方便业务扩展,代码非常简单,扩展非常灵活. 1. 目前手机号段有哪些 1.1 目前国内的手机号段主要集中在三大运营商手上,还有一些内部号段和虚拟号段 "中国电信": "133,153,189,180,181,177,173,199,174,14

Struts+spring+Hibernate 制作一个简单的登录验证

index.jsp <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"

.net 手写实现一个简单实体数据验证

源于一次面试中的面试题,代码如下所示: public class Product { public string Name { get; set; } public string Description { get; set; } public void Validate1() { if (string.IsNullOrEmpty(this.Name)) { throw new Exception("请输入名称"); } if (string.IsNullOrEmpty(this.De

PERL-MOJO写一个简单的登录验证页面

PERL果然是很牛掰的东西...   失业三年半了.还是想keep一下perl的兴趣. use Mojolicious::Lite; my $password='1253'; get '/login' => sub {     my $self = shift;     $self->render('login'); }; post '/login' => sub {     my $self = shift;         $self->render('loged',     

编写一个简单登录验证需要记录日志,Servlet中的Cookie

登录验证并记录日志 之前介绍了如何使用Server.mysql.tomcat等知识点编写了一个简单的登录验证.但是现在有了一个新的需求,我想要在登录成功的时候往数据库记录一条日志,登录失败的时候也要记录一下.这个日志要记录用户名.用户的IP地址.登录的时间.还有成功或失败的状态标识. 所以现在需要增加一个表格,用于存储日志信息,如图: 因为大部分思路和之前的写登录验证差不多,只是多了个记录日志,所以我这里就不赘述实现的思路了,直接上代码. 1. 首先需要使用html编写出页面,代码示例: CSS

Shiro处理简单的身份验证的分析及实例

在两天在看Shiro,开涛兄的教程还是写的比较易读,差不多看了一天吧,就准备拿来用了. 可能是想的太简单了,在用的时候确实碰到一些问题,就拿最简单的身份验证来说吧: 需要说明的是,这里是集成在Spring中使用,身份验证我直接使用了Shiro提供的 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 如果url应用了该拦截器,那么处理流程是大致这样的: 由于之前用markdown花的流程图显示不下,所以还是改成图片形式了. 比如我

.NET WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制

项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口.以前没玩过webAPI,但是领导要求必须用这个(具体原因鬼知道),只好硬着头皮上了. 最近刚做完权限这一块,分享出来给大家.欢迎各种吐槽批判践踏... 先说说用户身份的识别,简单的做了一个token机制.用户登录,后台产生令牌,发放令牌,用户携带令牌访问... 1.cache管理类,由于博主使用的HttpRuntime.Cache来存储token,IIS重启或者意外关闭等情况会造成cache清空,只好在数据库做了cache

WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制

.NET WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制 项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口.以前没玩过webAPI,但是领导要求必须用这个(具体原因鬼知道),只好硬着头皮上了. 最近刚做完权限这一块,分享出来给大家.欢迎各种吐槽批判践踏... 先说说用户身份的识别,简单的做了一个token机制.用户登录,后台产生令牌,发放令牌,用户携带令牌访问... 1.cache管理类,由于博主使用的HttpR