c# WebApi之身份驗證:Basic基礎認證
WebApi相關文章:
為什麼需要身份認證
身份認證是為了提高介面訪問的安全性,如果沒有身份驗證,那麼任何匿名使用者只要知道伺服器的url,就可以隨意訪問伺服器,從而訪問或者操作資料庫,這會是很恐怖的事。
什麼是Basic基礎認證
Basic基礎認證是一種簡單的使用者名稱、密碼驗證過程,它的主要原理是加密使用者資訊,生成票據,每次需要身份驗證時將票據帶過來驗證,實現步驟為:
- 使用者登入,登入成功後將生成的票據返回到前端;
- 前端登入成功後,收到票據資訊,跳轉到主頁面,並且吧票據一併帶過去,存入Session;
- 在需要請求頁面,把票據資訊加入到請求的Head裡面,將票據資訊隨著請求一起傳送到服務端去;
- 在WebApi服務裡面定義一個類,繼承AuthorizeAttribute類,然後重寫父類的OnAuthorization方法,在OnAuthorization方法裡面取到當前http請求的Head,從Head裡面取到我們前端傳過來的票據資訊。解密票據資訊,從解密的資訊裡面得到使用者名稱和密碼,然後驗證使用者名稱和密碼是否正確。如果正確,表示驗證通過,否則返回自定義錯誤資訊。
Basic基礎認證的程式碼示例:
首先新建兩個專案:Web測試站點、WebApi站點
1.1、在Web測試站點,新增一個登入頁面:
<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>
登入請求的ajax:
$(function () {
$("#btn_login").click(function () {
$.ajax({
type: "get",
url: "http://localhost:61593/api/account/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、對應的WebApi站點的,登入的Api介面:
/// <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(0, strUser, DateTime.Now,
DateTime.Now.AddHours(1), 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 == "123456")
{
return true;
}
else
{
return false;
}
}
自定義UserInfo實體:
public class UserInfo
{
public bool bRes { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Ticket { get; set; }
}
新建的WebApi需要配置一下路由,開啟App_Start資料夾下的WebApiConfig.cs檔案,新增一條路由資訊:
public static void Register(HttpConfiguration config)
{
//解決跨域訪問問題
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi1",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
2.1、在Web測試站點新增一個用於跳轉測試的index主頁面
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
</head>
<body>
測試結果:
<div id="div_test">
hello world
</div>
當前登入使用者:
<div id="username">
@ViewBag.UserName
</div>
</body>
</html>
ajax請求:
<script>
var ApiUrl = "http://localhost:61593/";
$(function () {
$.ajax({
type: "get",
url: ApiUrl + "api/Account/GetAllData",
data: {},
beforeSend:function(XHR){ XHR.setRequestHeader('Authorization','BasicAuth @ViewBag.Ticket')
},
success: function (data, status) {
if (status == "success") {
$("#div_test").html(data);
}
},
error: function (e) {
$("#div_test").html("Error");
}
});
});
</script>
這裡需要注意在beforeSend方法裡面,向請求的報文頭裡面增加票據資訊,用於把Ticket資訊一同帶到伺服器:
XHR.setRequestHeader(‘Authorization’,’BasicAuth @ViewBag.Ticket’)
2.2、index頁面的action,接收傳遞過來的票據資料,存入Session
public ActionResult Index(string UserName, string Ticket)
{
if (UserName != null)
{
Session["UserName"] = UserName;
}
if (Ticket != null)
{
Session["Ticket"] = Ticket;
}
ViewBag.UserName = Session["UserName"];
ViewBag.Ticket = Session["Ticket"];
return View();
}
2.3、對應的Api介面:
public class AccountController : ApiController
{
/// <summary>
/// 得到所有資料
/// </summary>
/// <returns>返回資料</returns>
[HttpGet]
[RequestAuthorize]
public string GetAllData()
{
return "Success";
}
}
WebAip預設是沒有開啟Session,需要手動開啟:
在WebApi站點,開啟Global.asax檔案,重寫Init()方法
public override void Init()
{
this.PostAuthenticateRequest += (sender, e) => HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
base.Init();
}
或者:
public override void Init()
{
PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpContext.Current.SetSessionStateBehavior(
SessionStateBehavior.Required);
}
3.1、WebApi身份驗證部分(重點)
在WebApi站點,新增一個RequestAuthorizeAttribute.cs檔案,繼承AuthorizeAttribute,自定義此特性用於介面的身份驗證:
/// <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);
}
}
protected override void HandleUnauthorizedRequest(HttpActionContext actioncontext)
{
base.HandleUnauthorizedRequest(actioncontext);
var response = actioncontext.Response = actioncontext.Response ?? new HttpResponseMessage();
response.StatusCode = HttpStatusCode.Forbidden;
var content = new
{
code = -1,
success = false,
errs = new[] { "服務端拒絕訪問:你沒有許可權,或者掉線了" }
};
response.Content = new StringContent(Json.Encode(content), Encoding.UTF8, "application/json");
}
//校驗使用者名稱密碼(正式環境中應該是資料庫校驗)
private bool ValidateTicket(string encryptTicket)
{
//解密Ticket
var strTicket = FormsAuthentication.Decrypt(encryptTicket).UserData;
//從Ticket裡面獲取使用者名稱和密碼
var index = strTicket.IndexOf("&");
string strUser = strTicket.Substring(0, index);
string strPwd = strTicket.Substring(index + 1);
if (strUser == "admin" && strPwd == "123456")
{
return true;
}
else
{
return false;
}
}
}
3.2、使用的時候只需要在控制器前面加上自定義的身份驗證[RequestAuthorize]
/// <summary>
/// 得到所有資料
/// </summary>
/// <returns>返回資料</returns>
[HttpGet]
[RequestAuthorize]
public string GetAllData()
{
return "Success";
}
如果不攜帶票據或者票據無效,服務端拒絕訪問:
如果在控制器加了身份驗證,有些請求又不想使用驗證,可以在方法上面新增特性標註[AllowAnonymous]
[RequestAuthorize]
public class AccountController : ApiController
{
/// <summary>
/// 得到所有資料
/// </summary>
/// <returns>返回資料</returns>
[HttpGet]
public string GetAllData()
{
return "Success";
}
[AllowAnonymous]
public string getData()
{
return "data";
}
}