前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题。也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想想都恐怖。经过一番折腾,总算是加上了接口的身份认证,在此记录下,也给需要做身份认证的园友们提供参考。

一、为什么需要身份认证

  在前言里面,我们说了,如果没有启用身份认证,那么任何匿名用户只要知道了我们服务的url,就能随意访问我们的服务接口,从而访问或修改数据库。

   1、我们不加身份认证,匿名用户可以直接通过url随意访问接口:

  可以看到,匿名用户直接通过url就能访问我们的数据接口,最终会发生什么事,大家可以随意畅想。

    2、增加了身份认证之后,只有带了我们访问票据的请求才能访问我们的接口。

  例如我们直接通过url访问,会返回401

  如果是正常流程的请求,带了票据,就OK了。

  可以看到,正常流程的请求,会在请求报文的头里面增加Authorization这一项,它的值就是我们的Ticket票据信息。

二、Basic基础认证的原理解析

1、常见的认证方式

  我们知道,asp.net的认证机制有很多种。对于WebApi也不例外,常见的认证方式有      

  • FORM身份验证
  • 集成WINDOWS验证
  • Basic基础认证
  • Digest摘要认证 

  园子里很多关于WebApi认证的文章,各种认证方式都会涉及到,但感觉都不够细。这里也并不想去研究哪种验证方式适用哪种使用场景,因为博主还是觉得“贪多嚼不烂”,也可能是博主能力所限。对于认证机制,弄懂其中一种,其他的都能融会贯通。此篇就使用Basic基础认证来详细讲解下整个的过程。

  

2、Basic基础认证原理

  

  我们知道,认证的目的在于安全,那么如何能保证安全呢?常用的手段自然是加密。Basic认证也不例外,主要原理就是加密用户信息,生成票据,每次请求的时候将票据带过来验证。这样说可能有点抽象,我们详细分解每个步骤:

  1. 首先登陆的时候验证用户名、密码,如果登陆成功,则将用户名、密码按照一定的规则生成加密的票据信息Ticket,将票据信息返回到前端。
  2. 如果登陆成功,前端会收到票据信息,然后跳转到主界面,并且将票据信息也带到主界面的ActionResult里面(例如跳转的url可以这样写:/Home/Index?Ticket=Ticket)
  3. 在主界面的ActionResult里面通过参数得到票据信息Ticket,然后将Ticket信息保存到ViewBag里面传到前端。
  4. 在主界面的前端,发送Ajax请求的时候将票据信息加入到请求的Head里面,将票据信息随着请求一起发送到服务端去。
  5. 在WebApi服务里面定义一个类,继承AuthorizeAttribute类,然后重写父类的OnAuthorization方法,在OnAuthorization方法里面取到当前http请求的Head,从Head里面取到我们前端传过来的票据信息。解密票据信息,从解密的信息里面得到用户名和密码,然后验证用户名和密码是否正确。如果正确,表示验证通过,否则返回未验证的请求401。

  这个基本的原理。下面就按照这个原理来看看每一步的代码如何实现。

三、Basic基础认证的代码示例

  首先说下我们的示例场景

1、登录过程

  1.1、Web前端

<body>
<div style="text-align:center;">
<div>用户名:<input type="text" id="txt_username" /></div>
<div>密 码:<input type="password" id="txt_password" /></div>
<div><input type="button" value="登录" id="btn_login" class="btn-default" /></div>
</div>
</body>
$(function () {
$("#btn_login").click(function () {
$.ajax({
type: "get",
url: "http://localhost:27221/api/User/Login",
data: { strUser: $("#txt_username").val(), strPwd: $("#txt_password").val() },
success: function (data, status) {
if (status == "success") {
if (!data.bRes){
alert("登录失败");
return;
}
alert("登录成功");
            //登录成功之后将用户名和用户票据带到主界面
window.location = "/Home/Index?UserName=" + data.UserName + "&Ticket=" + data.Ticket;
}
},
error: function (e) {
},
complete: function () { } });
});
});

   1.2、登录的API接口

public class UserController : ApiController
{
/// <summary>
/// 用户登录
/// </summary>
/// <param name="strUser"></param>
/// <param name="strPwd"></param>
/// <returns></returns>
[HttpGet]
public object Login(string strUser, string strPwd)
{
if (!ValidateUser(strUser, strPwd))
{
return new { bRes = false };
}
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(, strUser, DateTime.Now,
DateTime.Now.AddHours(), true, string.Format("{0}&{1}", strUser, strPwd),
FormsAuthentication.FormsCookiePath);
//返回登录结果、用户信息、用户验证票据信息
var oUser = new UserInfo { bRes = true, UserName = strUser, Password = strPwd, Ticket = FormsAuthentication.Encrypt(ticket) };
//将身份信息保存在session中,验证当前请求是否是有效请求
HttpContext.Current.Session[strUser] = oUser;
return oUser;
} //校验用户名密码(正式环境中应该是数据库校验)
private bool ValidateUser(string strUser, string strPwd)
{
if (strUser == "admin" && strPwd == "")
{
return true;
}
else
{
return false;
}
}
} public class UserInfo
{
public bool bRes { get; set; } public string UserName { get; set; } public string Password { get; set; } public string Ticket { get; set; }
}

  这里有一点需要注意的是,因为WebApi默认是没有开启Session的,所以需要我们作一下配置,手动去启用session。如何开启WebApi里面的Session,请参考:http://www.cnblogs.com/tinya/p/4563641.html

  正如上面的原理部分说的,登录如果失败,则直接返回;如果成功,则将生成的票据Ticket带到前端,传到主界面/Home/Index,下面,我们就来看看主界面Home/Index。

2、/Home/Index主界面

 public class HomeController : Controller
{
// GET: Home
public ActionResult Index(string UserName, string Ticket)
{
ViewBag.UserName = UserName;
ViewBag.Ticket = Ticket;
return View();
}
}
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<script src="~/Content/jquery-1.9.1.js"></script>
<link href="~/Content/bootstrap/css/bootstrap.css" rel="stylesheet" />
<script src="~/Content/bootstrap/js/bootstrap.js"></script>
<script src="~/Scripts/Home/Index.js"></script>
<script type="text/javascript">
//打开页面的时候保存票据信息
var UserName = '@ViewBag.UserName';
var Ticket = '@ViewBag.Ticket';
</script>
</head>
<body>
<div>当前登录用户:'@ViewBag.UserName'</div> <div id="div_test"> </div>
</body>
</html>
$(function () {
$.ajax({
type: "get",
url: "http://localhost:27221/api/Charging/GetAllChargingData",
data: {},
beforeSend: function (XHR) {
//发送ajax请求之前向http的head里面加入验证信息
XHR.setRequestHeader('Authorization', 'BasicAuth ' + Ticket);
},
success: function (data, status) {
if (status == "success") {
$("#div_test").html(data);
}
},
error: function (e) {
$("#div_test").html("Error");
},
complete: function () { } });
});

  这里需要说明的是,我们在发送ajax请求之前,通过 XHR.setRequestHeader('Authorization', 'BasicAuth ' + Ticket);这一句向请求的报文头里面增加票据信息。就是因为这里加了这一句,所以才有我们下图中的红线部分:

  

3、WebApiCORS验证部分(重点)

  我们看到,上面的/Home/Index页面里面发送了ajax请求去访问服务的 http://localhost:27221/api/Charging/GetAllChargingData这个接口,那么我们在WebApi里面怎么去验证这个请求和合法的请求呢?接下来我们重点看看验证的这个过程。

  3.1、在WebApiCORS项目里面自定义一个类RequestAuthorizeAttribute,去继承我们的AuthorizeAttribute这个类。然后重写OnAuthorization方法,在这个方法里面取到请求头的Ticket信息,然后校验用户名密码是否合理。

/// <summary>
/// 自定义此特性用于接口的身份验证
/// </summary>
public class RequestAuthorizeAttribute : AuthorizeAttribute
{
//重写基类的验证方式,加入我们自定义的Ticket验证
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//从http请求的头里面获取身份验证信息,验证是否是请求发起方的ticket
var authorization = actionContext.Request.Headers.Authorization;
if ((authorization != null) && (authorization.Parameter != null))
{
//解密用户ticket,并校验用户名密码是否匹配
var encryptTicket = authorization.Parameter;
if (ValidateTicket(encryptTicket))
{
base.IsAuthorized(actionContext);
}
else
{
HandleUnauthorizedRequest(actionContext);
}
}
//如果取不到身份验证信息,并且不允许匿名访问,则返回未验证401
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);
}
} //校验用户名密码(正式环境中应该是数据库校验)
private bool ValidateTicket(string encryptTicket)
{
//解密Ticket
var strTicket = FormsAuthentication.Decrypt(encryptTicket).UserData; //从Ticket里面获取用户名和密码
var index = strTicket.IndexOf("&");
string strUser = strTicket.Substring(, index);
string strPwd = strTicket.Substring(index + ); if (strUser == "admin" && strPwd == "")
{
return true;
}
else
{
return false;
}
}
}

  3.2、在具体的Api接口增加我们上面自定义类的特性

 [RequestAuthorize]
public class ChargingController : ApiController
{
/// <summary>
/// 得到所有数据
/// </summary>
/// <returns>返回数据</returns>
[HttpGet]
public string GetAllChargingData()
{
return "Success";
} /// <summary>
/// 得到当前Id的所有数据
/// </summary>
/// <param name="id">参数Id</param>
/// <returns>返回数据</returns>
[HttpGet]
public string GetAllChargingData(string id)
{
return "ChargingData" + id;
} }

  增加了特性标注之后,每次请求这个API里面的接口之前,程序会先进入到我们override过的 OnAuthorization()方法里面,验证通过之后,才会进到相应的方法里面去执行,否则返回401。

四、优化

   通过上面的几步,基本就能达到我们想要的身份认证的效果,但是总是感觉不太方便,主要不太方便的点有以下几个。

  1. 每次新建一个API,对应的接口上面都要标注 [RequestAuthorize]这个一个东西,感觉好麻烦。
  2. 每次发送ajax请求,都要在beforeSend事件里面加 XHR.setRequestHeader('Authorization', 'BasicAuth ' + Ticket);这个,感觉也麻烦。
  3. 如果有些WebApi服务的某些方法,我们不想使用这个验证,让它可以匿名用户验证(比如我们的登录方法Login)。该怎么处理呢。

  关于以上两点,我们优化下

1、解决API的问题

  在API里面加一个公共的父类,在父类上面标注 [RequestAuthorize]

namespace WebApiCORS.Controllers
{
[RequestAuthorize]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class BaseApiController : ApiController
{
}
}
namespace WebApiCORS.Controllers
{
public class ChargingController : BaseApiController
{
/// <summary>
/// 得到所有数据
/// </summary>
/// <returns>返回数据</returns>
[HttpGet]
public string GetAllChargingData()
{
return "Success";
} /// <summary>
/// 得到当前Id的所有数据
/// </summary>
/// <param name="id">参数Id</param>
/// <returns>返回数据</returns>
[HttpGet]
public string GetAllChargingData(string id)
{
return "ChargingData" + id;
}
  }
}

  注意:我们登录的请求是不需要验证的,因为登录的时候还没有产生票据,所以登录的API不能够继承 BaseApiController

2、解决ajax的问题

  还记得我们在 JS组件系列——封装自己的JS组件,你也可以 这篇里面介绍的增加ajax的error事件的公共处理方法吗?我们是否也可以通过同样的机制去增加这个呢。新建一个文件Jquery_ajax_extention.js

(function ($) {
//1.得到$.ajax的对象
var _ajax = $.ajax;
$.ajax = function (options) {
//2.每次调用发送ajax请求的时候定义默认的error处理方法
var fn = {
error: function (XMLHttpRequest, textStatus, errorThrown) {
toastr.error(XMLHttpRequest.responseText, '错误消息', { closeButton: true, timeOut: , positionClass: 'toast-top-full-width' });
},
success: function (data, textStatus) { },
beforeSend: function (XHR) { },
complete: function (XHR, TS) { }
}
//3.扩展原生的$.ajax方法,返回最新的参数
var _options = $.extend({}, {
error: function (XMLHttpRequest, textStatus, errorThrown) {
fn.error(XMLHttpRequest, textStatus, errorThrown);
},
success: function (data, textStatus) {
fn.success(data, textStatus);
},
beforeSend: function (XHR) {
XHR.setRequestHeader('Authorization', 'BasicAuth ' + Ticket);
fn.beforeSend(XHR);
},
complete: function (XHR, TS) {
fn.complete(XHR, TS);
}
}, options);
//4.将最新的参数传回ajax对象
_ajax(_options);
};
})(jQuery);

  引用这个js后再发送ajax不必在每个请求的beforeSend里面写了。

3、解决特殊不想使用验证的方法

  如果我们某些方法不想使用验证,使得它可以让匿名用户访问,我们可以在方法的上面加特性标注 [AllowAnonymous] ,申明该方法运行匿名访问。比如:

  public class ChargingController : BaseApiController
{
/// <summary>
/// 得到所有数据
/// </summary>
/// <returns>返回数据</returns>
[HttpGet]
public string GetAllChargingData()
{
return "Success";
} /// <summary>
/// 得到当前Id的所有数据
/// </summary>
/// <param name="id">参数Id</param>
/// <returns>返回数据</returns>
[HttpGet]
[AllowAnonymous]
public string GetAllChargingData(string id)
{
return "ChargingData" + id;
}
  }

五、总结

  以上结合一个实例讲解了下Basic认证的实现原理以及简单使用,本文观点都是来自博主自己的理解,如果有不全面的地方,还望园友们斧正。如果本文能够或多或少帮到你,不妨帮忙推荐,博主一定继续努力~~

本文出自:http://www.cnblogs.com/landeanfen/p/5287064.html#!comments

WebApi 身份认证解决方案:Basic基础认证的更多相关文章

  1. c# WebApi之身份验证:Basic基础认证

    为什么需要身份认证 身份认证是为了提高接口访问的安全性,如果没有身份验证,那么任何匿名用户只要知道服务器的url,就可以随意访问服务器,从而访问或者操作数据库,这会是很恐怖的事. 什么是Basic基础 ...

  2. C#进阶系列——WebApi 身份认证解决方案:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想 ...

  3. WebApi身份认证解决方案:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想 ...

  4. C#进阶系列——WebApi身份认证解决方案:Basic基础认证 (转)

    http://www.cnblogs.com/landeanfen/p/5287064.html 前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人 ...

  5. (转)C# WebApi 身份认证解决方案:Basic基础认证

    原文地址:http://www.cnblogs.com/landeanfen/p/5287064.html 阅读目录 一.为什么需要身份认证 二.Basic基础认证的原理解析 1.常见的认证方式 2. ...

  6. #进阶系列——WebApi 身份认证解决方案:Basic基础认证

    阅读目录 一.为什么需要身份认证 二.Basic基础认证的原理解析 1.常见的认证方式 2.Basic基础认证原理 三.Basic基础认证的代码示例 1.登录过程 2./Home/Index主界面 3 ...

  7. Web API(七):Basic基础认证

    1.WebApi中为什么需要身份认证 我们在使用WebApi的时候,都是通过URL去获取数据.也就是说,任何人只要知道了URL地址,就能随意的访问后台的服务接口,就可以访问或者修改数据库数据了,这样就 ...

  8. WebApi 身份认 Basic基础认证

    <body> <div style="text-align:center;"> <div>用户名:<input type="te ...

  9. atitit.身份认证解决方案attilax总结

    atitit.身份认证解决方案attilax总结 1.1. 身份认证1 1.2.  basic认证1 1.2.1. 编程实现basic客户端2 1.3. digest认证机制3 1.4. SSL认证3 ...

随机推荐

  1. Windows API编程(SDK编程)配置VS2017——出现LNK 2019错误的win32项目如何解决

    最近刚入门SDK编程,在 我终于知道为什么windowsApi学的人这么少了 这篇文章中,确实发现了这样的问题,我的教程使用VS2013->Windows桌面->win32,就诞生了能使用 ...

  2. Linux内存管理 (5)slab分配器

    专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...

  3. Spring 对Controller异常的统一处理

    对于Controller的异常处理,分为两种,一种是对已知的异常处理,一种是未知的异常处理 1.定义自定义异常类 /** * @author hzc * */ public class UserNot ...

  4. 使用nvm管理node不同版本,安装,环境配置,切换不同版本的node版本

    文章包含以下内容: 一.下载地址 二.nvm-noinstall.zip安装 三.nvm-setup.zip安装 四.测试安装以及使用 一.下载地址 https://github.com/coreyb ...

  5. 【Android Studio安装部署系列】十九、Android studio使用SVN

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 在AndroidStudio中开发版本控制,除了Git就是SVN,和Eclipse不同,Android Studio没有提供单独的插 ...

  6. Windows系统pip安装whl包

    1.确保PIP的存在 2.CMD命令进入C:\Python34\Scripts里面后再执行PIP命令安装pip install wheel     # D:   和cd  地址 3.把文件最好放在\S ...

  7. SmartSql 常见问题

    常见问题 为什么不支持 Linq? SmartSql 希望 开发人员更多的接触 Sql ,获得绝对的控制权与安全感.所以目前没有计划支持 Code First 编程模式. 我想好了Sql怎么写,然后再 ...

  8. 记一个 DataBindings遇到的坑,当ComboBox同时绑定DataSource与DataBindings的时候,绑定的元素一定要同名

    下面的两个member1 一定要一样才行,不然会导致绑定异常 comboBox1.DisplayMember = "member1"; comboBox1.DataBindings ...

  9. Hexo优化 | 创建sitemap站点地图并向Google提交

    前言 站点地图是一种文件,您可以通过该文件列出您网站上的网页,从而将您网站内容的组织架构告知Google和其他搜索引擎.Sitemap 可方便管理员通知搜索引擎他们网站上有哪些可供抓取的网页.搜索引擎 ...

  10. python 题库1

    1. 生成一个1到50的大字符串,每个数字之间有个空格,例如1 2 3 4 ……50 解题思路: (1)声明一个空字符串变量用于保存生成的字符串 (2)使用for循环+range()函数生成一个1到5 ...