ASP.NET Web API 2系列(四):基於JWT的token身份認證方案

碼探長發表於2020-09-23

1.引言

通過前邊的系列教程,我們可以掌握WebAPI的初步運用,但是此時的API介面任何人都可以訪問,這顯然不是我們想要的,這時就需要控制對它的訪問,也就是WebAPI的許可權驗證。驗證方式非常多,本文就重點介紹一種常用的驗證方式:基於JWT的token身份認證方案。

2.前期回顧

Web API系列(一):初識API及手動搭建基本框架

Web API系列(二):靈活多樣的路由配置

Web API系列(三):新增介面詳細說明及測試

3.認識JWT

JWT是 JSON Web Token 的縮寫,是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON物件在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。

3.1 JWT工作流程

這裡我們通過一張圖瞭解它的工作流程。

從上圖中我們可以看出它是基於Token的身份認證,具體流程:客戶端攜帶使用者名稱和密碼請求訪問 - 伺服器校驗使用者憑據 - 應用提供一個token給客戶端 - 客戶端儲存token,並且在隨後的每一次請求中都帶著它 -伺服器校驗token並返回資料。

3.2JWT結構

JSON Web Token由三部分組成,它們之間用圓點(.)連線。這三部分分別是:

  • Header:頭部,它有token的型別(“JWT”)和演算法名稱(比如:HMAC SHA256或者RSA等等)兩部分組成;
  • Payload:荷載,它包含宣告(要求)。宣告是關於實體(通常是使用者)和其他資料的宣告;
  • Signature:簽名,目的是用來驗證頭部和載荷是否被非法篡改。

通過下圖,我們可以直觀的看到JWT的組成。

它本質上是一個獨立的身份驗證令牌,可以包含使用者標識、使用者角色和許可權等資訊,以及您可以儲存任何其他資訊(自包含)。任何人都可以輕鬆讀取和解析,並使用金鑰來驗證真實性。

4.具體實現

上文介紹了JWT的原理,讀者簡單瞭解即可,這裡我們通過具體程式碼來實現。

4.1安裝JWT包

通過NuGget管理工具安裝JWT包,如下圖

4.2新增LoginRequest、AuthInfo和HttpResult三個實體類

在MyWebAPI.Entities中新增相應類

LoginRequest實體

public class LoginRequest
{
    public string UserId { get; set; }
    public string Password { get; set; }
}

AuthInfo實體類

public class AuthInfo
{
    public string UserId { get; set; }
    public DateTime Expires { get; set; }
}

HttpResul實體類

public class HttpResult
{
    public bool Success { get; set; }
    public dynamic Data { get; set; }
    public string Message { get; set; }
}

4.3新增SystemController,並新增Login登入方法

具體程式碼如下:

[RoutePrefix("api/System")]
public class SystemController : ApiController
{
    [HttpPost, Route("Login")]
    public HttpResult Login([FromBody] LoginRequest loginRequest)
    {
        if (loginRequest == null) return new HttpResult() { Success = false, Message = "登入資訊為空!" };

        #region 通過資料庫判斷登入資訊是否正確(這裡簡化判斷)

        if (loginRequest.UserId != "admin" || loginRequest.Password != "admin")
        {
            return new HttpResult() { Success = false, Message = "使用者名稱和密碼不正確!" };
        }
        #endregion
        AuthInfo authInfo = new AuthInfo()
        {
            UserId = loginRequest.UserId,
            Expires = DateTime.Now.AddDays(1)
        };
        const string secretKey = "matanzhang";//口令加密祕鑰(應該寫到配置檔案中)
        byte[] key = Encoding.UTF8.GetBytes(secretKey);
        IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//加密方式
        IJsonSerializer serializer = new JsonNetSerializer();//序列化Json
        IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密
        IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);//JWT編碼
        var token = encoder.Encode(authInfo, key);//生成令牌

        return new HttpResult() { Success = true, Data = token,Message = "登入成功!"};
    }
}

4.4新增API過濾器ApiAuthorizeAttribute

具體程式碼如下:

public class ApiAuthorizeAttribute: AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        try
        {
            var authHeader = from t in actionContext.Request.Headers where t.Key == "auth" select t.Value.FirstOrDefault();
            var enumerable = authHeader as string[] ?? authHeader.ToArray();
            string token = enumerable.FirstOrDefault();
            if (string.IsNullOrEmpty(enumerable.FirstOrDefault())) return false;
            const string secretKey = "matanzhang";//口令加密祕鑰(應該寫到配置檔案中)
            byte[] key = Encoding.UTF8.GetBytes(secretKey);
            IJsonSerializer serializer = new JsonNetSerializer();
            IDateTimeProvider provider = new UtcDateTimeProvider();
            IJwtValidator validator = new JwtValidator(serializer, provider);
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
            //解密
            var authInfo = decoder.DecodeToObject<AuthInfo>(token, key, verify: true);
            if (authInfo != null)
            {
                //判斷口令過期時間
                if (authInfo.Expires < DateTime.Now)
                {
                    return false;
                }
                actionContext.RequestContext.RouteData.Values.Add("auth", authInfo);
                return true;
            }
        }
        catch (Exception e)
        {

        }
        return false;
    }
    /// <summary>
    /// 處理授權失敗的請求
    /// </summary>
    /// <param name="actionContext"></param>
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        var erModel = new HttpResult()
        {
            Success = false,
            Message = "身份認證不正確!"
        };
        actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, erModel, "application/json");
    }
}

4.5在StudentController中新增過濾屬性ApiAuthorize

具體如下:

[RoutePrefix("api/Student"),ApiAuthorize]
public class StudentController : ApiController
{
    private static readonly List<Student> StudentList = new List<Student>()
    {
        new Student() {Id = "001", Name = "張三", Sex = "男", Age = 19, Dept = "軟體學院"},
        new Student() {Id = "002", Name = "李麗", Sex = "女", Age = 19, Dept = "資環學院"}
    };

    [HttpGet]
    public IEnumerable<Student> Get()
    {
        return StudentList;
    }

    [HttpGet, Route("GetByDept/{dept}")]
    public IEnumerable<Student> GetByDept(string dept)
    {
        List<Student> tempList = StudentList.Where(p => p.Dept == dept).ToList();
        return tempList;
    }
    [HttpGet]
    public Student Get(string id)
    {
        List<Student> tempList = StudentList.Where(p => p.Id == id).ToList();
        return tempList.Count == 1 ? tempList.First() : null;
    }

    [HttpPost]
    public bool Post([FromBody] Student student)
    {
        if (student == null) return false;
        if (StudentList.Where(p => p.Id == student.Id).ToList().Count > 0) return false;
        StudentList.Add(student);
        return true;
    }

    [HttpPut]
    public bool Put(string id, [FromBody] Student student)
    {
        if (student == null) return false;
        List<Student> tempList = StudentList.Where(p => p.Id == id).ToList();
        if (tempList.Count == 0) return false;
        Student originStudent = tempList[0];
        originStudent.Name = student.Name;
        originStudent.Sex = student.Sex;
        originStudent.Age = student.Age;
        originStudent.Dept = student.Dept;
        return true;
    }

    [HttpDelete]
    public bool Delete(string id)
    {
        List<Student> tempList = StudentList.Where(p => p.Id == id).ToList();
        if (tempList.Count == 0) return false;
        StudentList.Remove(tempList[0]);
        return true;
    }
}

依照步驟新增相關程式碼,此時就完成了JWT驗證的新增。

5.通過PostMan測試程式

執行VS,檢視相關API介面,如下圖所示。

登入前,測試Get:http://localhost:44321/api/Student介面,返回結果如下圖所示。

登入,測試Post:http://localhost:44321/api/System/Login介面,返回結果如下圖所示。

登入後,測試Get:http://localhost:44321/api/Student介面,返回結果如下圖所示。

在APIController上新增許可權驗證後,訪問相應介面時,需要在header裡面新增auth屬性(token),這樣就完成了身份認證。

6.總結

本文介紹了JWT的原理,然後通過程式碼完成了相應例項教程,博文中的原始碼可以通過筆者GitHUb獲取。博文寫作不易希望多多支援,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!也可以通過微信公眾搜尋“碼探長”,聯絡筆者!

掃描新增下方的微信公眾號,獲取更多福利和乾貨!也可通公眾號(碼探長)聯絡探長,期待與你相遇!!!

相關文章