1. 程式人生 > >ASP.NET MVC5+EF6+EasyUI 後臺管理系統(65)-MVC WebApi 用戶驗證 (1)

ASP.NET MVC5+EF6+EasyUI 後臺管理系統(65)-MVC WebApi 用戶驗證 (1)

screen 屬性 access override jquery true mod 解析 action

系列目錄

前言:

WebAPI主要開放數據給手機APP,其他需要得知數據的系統,或者軟件應用,所以移動端與系統的數據源往往是相通的。

Web 用戶的身份驗證,及頁面操作權限驗證是B/S系統的基礎功能,一個功能復雜的業務應用系統,通過角色授權來控制用戶訪問

本文通過Basic 方式進行基礎認證Mvc的Controller基類及Action的權限驗證來實現Web系統登錄,Mvc前端權限校驗以及WebApi服務端的訪問校驗功能,本文主要作為本人備忘使用,如能給予人幫助,深感榮幸,歡迎討論和指正,下面梳理一下驗證的流程

開發環境:

VS2015+無數據庫(模擬數據)

知識點:

  1. WebApi簡單使用
  2. 用戶校驗
  3. 同域訪問
  4. 跨域訪問

驗證流程:

技術分享

1.WebApi服務端接收訪問請求,需要做安全驗證處理,驗證處理步驟具體如下:

1) 如果是合法的Http請求,在Http請求頭中會有用戶身份的票據信息(如果是跨域那麽無法在請求頭中添加票據),服務端會讀取票據信息,並校驗票據信息是否完整有效,如果滿足校驗要求,則進行業務數據的處理,並返回給請求發起方;

2) 如果沒有票據信息,或者票據信息不是合法的,則返回“未授權的訪問”異常消息給前端,由前端處理此異常。

2. 登錄及權限驗證流程

1) 用戶打開瀏覽器,並在地址欄中輸入頁面請求地址,提交;

2) 瀏覽器解析Http請求,發送到Web服務器;Web服務器驗證用戶請求,首先判斷是否有登錄的票據信息;

3) 用戶沒有登錄票據信息,則跳轉到登錄頁面;

4) 用戶輸入用戶名和密碼信息;

5) 瀏覽器提交登錄表單數據給Web服務器;

6) Web服務需要驗證用戶名和密碼是否匹配,發送api請求給api服務器;

7) api用戶賬戶服務根據用戶名,讀取存儲在數據庫中的用戶資料,判斷密碼是否匹配;

7.1)如果用戶名和密碼不匹配,則提示密碼錯誤等信息,然該用戶重新填寫登錄資料;

7.2)如果驗證通過,則保存用戶票據信息;

8)

3.如果用戶有登錄票據信息,則跳轉到用戶請求的頁面;

9) 驗證用戶對當前要操作的頁面或頁面元素是否有權限操作,首先需要發起api服務請求,獲取用戶的權限數據;

10). api用戶權限服務根據用戶名,查找該用戶的角色信息,並計算用戶權限列表,封裝為Json數據並返回;

11). 當用戶有權限操作頁面或頁面元素時,跳轉到頁面,並由頁面Controller提交業務數據處理請求到api服務器; 如果用戶沒有權限訪問該頁面或頁面元素時,則顯示“未授權的訪問操作”,跳轉到系統異常處理頁面。

12). api業務服務處理業務邏輯,並將結果以Json 數據返回;

13). 返回渲染後的頁面給瀏覽器前端,並呈現業務數據到頁面;

14). 用戶填寫業務數據,或者查找業務數據;

15). 當填寫或查找完業務數據後,用戶提交表單數據;

16). 瀏覽器腳本提交get,post等請求給web服務器,由web服務器再次解析請求操作,重復步驟2的後續流程;

17). 當api服務器驗證用戶身份是,沒有可信用戶票據,系統提示“未授權的訪問操作”,跳轉到系統異常處理頁面。

開始:

1.添加一個空的WebApi,無身份驗證WebApi

技術分享

2.新建Account控制器 AccountController

技術分享
using Apps.Common;
using Apps.WebApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Security;
namespace Apps.WebApi.Controllers
{
    public class AccountController : ApiController
    {
        [HttpGet]
        public object Login(string userName, string password)
        {
            //實際場景應該到數據庫進行校驗
            if (userName != "123" && password!="123")
            {
                return Json(JsonHandler.CreateMessage(0, "用戶名或密碼錯誤"));
            }
            FormsAuthenticationTicket token = new FormsAuthenticationTicket(0, userName, DateTime.Now,
                            DateTime.Now.AddHours(1), true, string.Format("{0}&{1}", userName, password),
                            FormsAuthentication.FormsCookiePath);
            //返回登錄結果、用戶信息、用戶驗證票據信息
            var Token = FormsAuthentication.Encrypt(token);
            //將身份信息保存在session中,驗證當前請求是否是有效請求
            HttpContext.Current.Session[userName] = Token;

            return Json(JsonHandler.CreateMessage(1, Token));
        }

    }
}
技術分享

對用戶名和密碼進行校驗,這裏沒有數據庫演示,所以直接是進行固定匹配,帳號123,密碼123(可參考19節用戶登錄,獲得數據庫的校驗方式)

登錄失敗:返回錯誤提示

登錄成功:返回Token並保存Token到 Session

可見代碼中包含Session的操作,但是Webapi默認是不支持Session的,所以我們需要在Global加載時候添加對Session的支持,不然運行調用會直接異常

    protected void Application_PostAuthorizeRequest()
    {
        HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
    }

3.運行Webapi

輸入http://localhost:13743/help可以看到,我們的接口已經在webapi help列出,並可以查看調用方式(VS2012可能沒有自動生成WebApi Help,需要從Nuget包獲得)

技術分享

4.同域調用

在Home的Index.cshtm添加登錄代碼

技術分享
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<style>html,body{height:100%}.box{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=‘#6699FF‘, endColorstr=‘#6699FF‘);background-image:linear-gradient(bottom,#69F 0,#69F 100%);background-image:-o-linear-gradient(bottom,#69F 0,#69F 100%);background-image:-moz-linear-gradient(bottom,#69F 0,#69F 100%);background-image:-webkit-linear-gradient(bottom,#69F 0,#69F 100%);background-image:-ms-linear-gradient(bottom,#69F 0,#69F 100%);margin:0 auto;position:relative;width:100%;height:100%}.login-box{width:100%;max-width:500px;height:400px;position:absolute;top:50%;margin-top:-200px}@@media screen and (min-width:500px){.login-box{left:50%;margin-left:-250px}}.form{width:100%;max-width:500px;height:275px;margin:25px auto 0 auto;padding-top:25px}.login-content{height:300px;width:100%;max-width:500px;background-color:rgba(255,250,2550,.6);float:left}.input-group{margin:0 0 30px 0!important}.form-control,.input-group{height:40px}.form-group{margin-bottom:0!important}.login-title{padding:20px 10px;background-color:rgba(0,0,0,.6)}.login-title h1{margin-top:10px!important}.login-title small{color:#fff}.link p{line-height:20px;margin-top:30px}.btn-sm{padding:8px 24px!important;font-size:16px!important}
</style>

<div class="box" style="margin:100px;height:400px;width:500px;">
    <div class="login-box">
        <div class="login-title text-center">
            <h1><small>登錄</small></h1>
        </div>
        <div class="login-content ">
            <div class="form">
                <form action="#" method="post">
                    <div class="form-group">
                        <div class="col-xs-12  ">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <input type="text" id="username" name="username" class="form-control" placeholder="用戶名">
                            </div>
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-xs-12  ">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
                                <input type="text" id="password" name="password" class="form-control" placeholder="密碼">
                            </div>
                        </div>
                    </div>
                    <div class="form-group form-actions">
                        <div class="col-xs-4 col-xs-offset-4 ">
                            <button type="button" id="Login" class="btn btn-sm btn-info"><span class="glyphicon glyphicon-off"></span> 登錄</button>
                        </div>
                    </div>
                
                </form>
            </div>
        </div>
    </div>
</div>
<script>
    $(function () {
        $("#Login").click(function () {
            $.ajax({
                type: "get",
                url: "/api/Account/Login",
                data: { userName: $("#username").val(), password: $("#password").val() },
                success: function (data, status) {
                        if (data.type==0) {
                            alert("登錄失敗");
                            return;
                        }
                        alert("登錄成功:Token"+data.message);
                },
                error: function (e) {
                    alert("登錄失敗!");
                },
                complete: function () {

                }
            });
        });
    });
</script>
技術分享

瀏覽器中運行/Home/Index

技術分享

成功取得Token

5.跨域訪問

同域名訪問,一般系統任務這是安全的,可以信任的,所以不需要做過多的考慮,這是我們來看看跨域的情況

1.便於好記,把Apps.WebApi的端口設置為固定的8866

技術分享

2.新建一個新的Web MVC普通無用戶驗證站點Apps.Web 設置端口為4455

把8866的Home/index登錄界面代碼復制到4455下的Home/index,修改訪問URL

url: "http://localhost:8866/api/Account/Login"

3.設置解決方案為多項目啟動,同時啟動4455,8866

這樣才用讓4455去訪問6655的API,不然絕對報404

技術分享

技術分享

訪問成功,但是沒有返回值,jquery顯示jquery的jsonp格式有callback返回

設置Ajax的dataType 為Jsonp

dataType:"jsonp",

再次運行,帶回來的值正常

技術分享

但是結果並沒有彈出token並提示一個js錯誤

技術分享

到這裏真是一波三折

因為返回的值是:{"Id":"123"}

然而Jsonp需要你返回:jQuery*([{"Id":123"}])

4.讓WebApi支持跨域返回的格式

註冊一個全局屬性

技術分享
using System.Net.Http;
using System.Text;
using System.Web;
using System.Web.Http.Filters;

namespace Apps.WebApi.Core
{
    public class JsonCallbackAttribute : ActionFilterAttribute
    {
        private const string CallbackQueryParameter = "callback";

        public override void OnActionExecuted(HttpActionExecutedContext context)
        {
            var callback = string.Empty;

            if (IsJsonp(out callback))
            {
                var jsonBuilder = new StringBuilder(callback);

                jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
                context.Response.Content = new StringContent(jsonBuilder.ToString());
            }

            base.OnActionExecuted(context);
        }

        private bool IsJsonp(out string callback)
        {
            callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

            return !string.IsNullOrEmpty(callback);
        }
    }
}
技術分享
GlobalConfiguration.Configuration.Filters.Add(new JsonCallbackAttribute());

再次運行:

技術分享

本節結束,下節再學習怎麽利用Token進行訪問獲得權限

參考資料:

http://stackoverflow.com/questions/9594229/accessing-session-using-asp-net-web-api

http://stackoverflow.com/questions/23698804/asp-net-mvc-with-forms-auth-and-webapi-with-basic-auth

https://weblog.west-wind.com/posts/2013/apr/18/a-webapi-basic-authentication-authorization-filter

http://stackoverflow.com/questions/17121964/asp-net-web-api-restful-web-service-basic-authentication

http://www.cnblogs.com/Kummy/p/3767269.html

實例代碼下載 訪問密碼 13df

ASP.NET MVC5+EF6+EasyUI 後臺管理系統(65)-MVC WebApi 用戶驗證 (1)