1. 程式人生 > >c# WebApi之身份驗證:Basic基礎認證

c# WebApi之身份驗證:Basic基礎認證

WebApi相關文章:

為什麼需要身份認證

身份認證是為了提高介面訪問的安全性,如果沒有身份驗證,那麼任何匿名使用者只要知道伺服器的url,就可以隨意訪問伺服器,從而訪問或者操作資料庫,這會是很恐怖的事。

什麼是Basic基礎認證

Basic基礎認證是一種簡單的使用者名稱、密碼驗證過程,它的主要原理是加密使用者資訊,生成票據,每次需要身份驗證時將票據帶過來驗證,實現步驟為:

  1. 使用者登入,登入成功後將生成的票據返回到前端;
  2. 前端登入成功後,收到票據資訊,跳轉到主頁面,並且吧票據一併帶過去,存入Session;
  3. 在需要請求頁面,把票據資訊加入到請求的Head裡面,將票據資訊隨著請求一起傳送到服務端去;
  4. 在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";
    }
}