Asp.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(, index);
string password = strTicket.Substring(index + );
//取得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 = , data = "", msg = "用户名密码错误" });
}
FormsAuthenticationTicket token = new FormsAuthenticationTicket(, uName, DateTime.Now, DateTime.Now.AddHours(), true, $"{uName}&{uPassword}", FormsAuthentication.FormsCookiePath);
//返回登录结果、用户信息、用户验证票据信息
var _token = FormsAuthentication.Encrypt(token);
//将身份信息保存在session中,验证当前请求是否是有效请求
LoginID = uName;
TokenValue = _token;
HttpContext.Current.Session[LoginID] = _token;
return Json(new { ret = , 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()>)
{
var ret = new Result()
{
Ret = ,
Code = "",
Msg = "获取数据成功",
Data = JsonConvert.SerializeObject(data) };
return ret;
}
else
{
var ret = new Result()
{
Ret = ,
Code = "",
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<=)
{
var ret = new Result()
{
Ret = ,
Code = "F",
Msg = "没有查到相关数据",
Data = "" };
return ret;
}
var bb = db.AddressAll.Where(x=>x.ID>).AsEnumerable();
var data = GetProvinceCity(bb,codeID).ToList();
if (data.Count() > )
{
var ret = new Result()
{
Ret = ,
Code = "",
Msg = "获取数据成功",
Data = JsonConvert.SerializeObject(data) };
return ret;
}
else
{
var ret = new Result()
{
Ret = ,
Code = "",
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=;i<;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=标题---------------------> 查询数据按标题
Asp.Net WebApi一个简单的Token验证的更多相关文章
- ASP.NET Web APIs 基于令牌TOKEN验证的实现(保存到DB的Token)
http://www.cnblogs.com/niuww/p/5639637.html 保存到DB的Token 基于.Net Framework 4.0 Web API开发(4):ASP.NET We ...
- ASP.NET WebApi 基于JWT实现Token签名认证
一.前言 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebServi ...
- 基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是在使用API的时候总会遇到跨域请求的问题, ...
- Asp.NetCore轻松学-业务重点-实现一个简单的手机号码验证
前言 本文纯干货,直接拿走使用,不用付费.在业务开发中,手机号码验证是我们常常需要面对的问题,目前市场上各种各样的手机号码验证方式,比如正则表达式等等,本文结合实际业务场景,在业务级别对手机号 ...
- ASP.NET Core 项目简单实现身份验证及鉴权
ASP.NET Core 身份验证及鉴权 目录 项目准备 身份验证 定义基本类型和接口 编写验证处理器 实现用户身份验证 权限鉴定 思路 编写过滤器类及相关接口 实现属性注入 实现用户权限鉴定 测试 ...
- ASP.NET WebApi总结之自定义权限验证
在.NET中有两个AuthorizeAttribute类, 一个定义在System.Web.Http命名空间下 #region 程序集 System.Web.Http, Version=5.2.3.0 ...
- asp.net 的一个简单进度条功能
我们先看下效果 我点击了按钮后他会显示进度页面,进度完成后,进度条消失,其实也是比较简单的了. 我们需要一个进度条代码文件ProgressBar.htm(注意:是没有head这些标签的) <sc ...
- asp.net mvc 微信公众号token验证
本人的公众号要申请成为开发者,必须经过token认证.微信公众号的官方代码只列出了PHP代码的实例,明显是歧视.net用户.我用的asp.net mvc中的web api,结果调了好久都没有成功,最后 ...
- ASP.NET制作一个简单的等待窗口
前一阵做一个项目,在处理报表的时候时间偏长,客户提出要做出一个等待窗口提示用户等待(页面太久没反映,用户还以为死了呢).在分析这一需求之后,觉得如果要实现像winform应用中的processbar太 ...
随机推荐
- QQ是怎样创造出来的?——解密好友系统的设计
本篇介绍笔者接触的第一个后台系统,从自身见闻出发,因此涉及的内容相对比较基础,后台大牛请自觉略过. 什么是好友系统? 简单的说,好友系统是维护用户好友关系的系统.我们最熟悉的好友系统案例当属QQ,实际 ...
- iOS核心动画高级技巧-2
3. 图层几何学 图层几何学 不熟悉几何学的人就不要来这里了 --柏拉图学院入口的签名 在第二章里面,我们介绍了图层背后的图片,和一些控制图层坐标和旋转的属性.在这一章中,我们将要看一看图层内部是如何 ...
- 力扣(LeetCode)按奇偶排序数组II 个人题解
给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数. 对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数:当 A[i] 为偶数时, i 也是偶数. 你可以返回任何满足上述条件的数组 ...
- python:调用bash
利用os模块 python调用Shell脚本,有三种方法: os.system(cmd)返回值是脚本的退出状态码 os.popen(cmd)返回值是脚本执行过程中的输出内容 commands.gets ...
- opencv 5 图像转换(3 重映射 仿射变换 直方图均衡化)
重映射 实现重映射(remap函数) 基础示例程序:基本重映射 //---------------------------------[头文件.命名空间包含部分]------------------- ...
- 一 linuk系统简介
开源软件 使用的自由,绝大多数开源软件免费 研究的自由,可以获得软件源代码 散播及改良的自由,可以自由传播 改良甚至销售 linuk应用领域 基于linuk的企业服务器 扫描踩点网站www.netcr ...
- 🙀Java 又双叒叕发布新版本,这么多版本如何灵活管理?
文章来源:http://1t.click/bjAG 前言 不知不觉 JDK13 发布已有两个月,不知道各位有没有下载学习体验一番?每次下载安装之后,需要重新配置一下 Java 环境变量.等到运行平时的 ...
- SQLServer2008R2(百度网盘)下载与安装教程
很久没有安装过这个了,今天安装有点生疏了,这里记录一下分享 分为三块块1.下载地址,2.安装图解 ,3.安装失败问题 1.sqlserver 2008 r2 百度下载地址链接:下载 cn_sql_s ...
- ES6扩展运算符...
对象的扩展运算符理解对象的扩展运算符其实很简单,只要记住一句话就可以: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中 let bar = { a: 1, b: 2 ...
- C语言I博客作业11
这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/CST2019-1/homework/10132 我在这个课程的 ...