1.引言
通過前邊的系列教程,我們可以掌握WebAPI的初步運用,但是此時的API介面任何人都可以訪問,這顯然不是我們想要的,這時就需要控制對它的訪問,也就是WebAPI的許可權驗證。驗證方式非常多,本文就重點介紹一種常用的驗證方式:基於JWT的token身份認證方案。
2.前期回顧
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獲取。博文寫作不易希望多多支援,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!也可以通過微信公眾搜尋“碼探長”,聯絡筆者!
掃描新增下方的微信公眾號,獲取更多福利和乾貨!也可通公眾號(碼探長)聯絡探長,期待與你相遇!!!