一站式WebAPI與認證授權服務

来源:https://www.cnblogs.com/podolski/archive/2020/04/20/12737463.html
-Advertisement-
Play Games

保護WEBAPI有哪些方法? 微軟官方文檔推薦了好幾個: Azure Active Directory Azure Active Directory B2C (Azure AD B2C)] IdentityServer4 前面兩個看著就覺得搞不太明白,第三個倒是非常常見,相關的文章也很多。不過這個東 ...


保護WEBAPI有哪些方法?

微軟官方文檔推薦了好幾個:

  • Azure Active Directory
  • Azure Active Directory B2C (Azure AD B2C)]
  • IdentityServer4

前面兩個看著就覺得搞不太明白,第三個倒是非常常見,相關的文章也很多。不過這個東西是獨立部署的,太重了,如果我就想寫一個簡單一點的API,把認證給包括的,是不是有好辦法?

準備

假設你的WEBAPI使用JWT TOKEN來保存你的認證信息,並且通過JWT TOKEN進行保護。那麼我們可以設計一個集成有認證授權的WEBAPI服務,一站式解決問題,代碼簡單且方便自行修改。

要點:

  1. 使用類似[Authorize]的授權,需要基於token中role這個Claim來實現。
  2. 密碼的保存需要進行特別設計。
  3. 用戶對象返回需要避免password和passwordhash的傳遞。

項目特點:

  1. RESTful設計(正常來說api的資源應該是複數userinfos,但是info應該就是不可數的,不糾結了。)
  2. 集成Swagger
  3. ASP.NET Core 3.1
  4. nullable設計
  5. EF Core
  6. 用戶許可權控制
  7. 密碼安全存儲
  8. Token實現與API集成
  9. 簡單易於理解

用戶實體類

所有認證之類的工作都在API這邊實現,因此我們需要一個userinfo類來進行處理。

[DataContract]
[Table("userinfo")]
public class UserInfo
{
    [DataMember]
    [Key]
    public string UserId { get; set; } = default!;
    //傳輸的過程中會用到密碼,但是這個密碼不應該被存入資料庫中。
    [NotMapped]
    [DataMember]
    public string? Password { get; set; }
    //傳輸的過程中不會用到密碼哈希值,但是哈希值需要存入資料庫中。
    [IgnoreDataMember]
    public string? PasswordHash { get; set; }
    [DataMember]
    public string? Role { get; set; }

    public static string GetRole(string? role)
    {
        if (string.IsNullOrWhiteSpace(role)) return "User";
        return role.ToLower() switch
        {
            "administrator" => "Administrator",
            "supervisor" => "Supervisor",
            _ => "User"
        };
    }
}
  • 使用json進行序列化,[DataContract]不是必須的,我一般是不喜歡寫這個東西,不寫的話,那麼所有的public屬性和欄位都會被序列化;如果標記了[DataContract],那麼只有標記有[DataMember]的會被序列化,使用[IgnoreDataMember]可以阻止序列化。
  • 使用了EF Core用來持久化,標記[NotMapped]指示屬性不被映射到資料庫中,一般來說,資料庫不應該直接保存密碼。

令牌發放

具體實現TokenController如下。

[AllowAnonymous]
[HttpPost]
public ActionResult Post(UserInfo login)
{
    ActionResult response = BadRequest("登錄失敗,請檢查用戶名和密碼");
    var user = AuthenticateUser(login);

    if (user != null)
    {
        var tokenString = GenerateJSONWebToken(user);
        response = Ok(new { access_token = tokenString, role = user.Role });
    }

    return response;
}

private string GenerateJSONWebToken(UserInfo userInfo)
{
    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

    var claims = new[] {
        new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserName),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(ClaimTypes.Role, userInfo.Role),
    };

    var token = new JwtSecurityToken(null,
        null,
        claims,
        expires: DateTime.Now.AddMinutes(120),
        signingCredentials: credentials);

    return new JwtSecurityTokenHandler().WriteToken(token);
}

private UserInfo? AuthenticateUser(UserInfo login)
{
    UserInfo? user = null;
    if (string.IsNullOrWhiteSpace(login.Password)) return user;

    using (var context = new ManageDataContext())
    {
        var result = context.UserInfos.Where(w => w.UserName.ToLower() == login.UserName.ToLower()).FirstOrDefault();
        if (result != null)
            if (PasswordStorage.VerifyPassword(login.Password, result.PasswordHash!)) user = result;
    }

    return user;
}

上面的類標誌有AllowAnonymous,表示這個類是可以匿名訪問的,用戶先請求post請求token,然後再攜帶token訪問其他API。

上面用到一個PasswordStorage的庫,這個庫使用了加鹽哈希的形式存儲了密碼,實踐上比較可靠。值得一提的是它的VerifyPassword()函數,使用的比較演算法很巧妙,我貼在了文末,推薦大家閱讀。

受保護的API

被保護的用戶管理API如下,只貼了一小部分:

[EnableCors("AllowAll")]
[Route("api/[controller]")]
//只有角色為Admin可以訪問
[Authorize(Roles = "Admin")]
//如果需要增加種子數據,可以註釋上面這行,取消註釋下麵這一行
//[AllowAnonymous]
[ApiController]
public class UserInfoController : ControllerBase
{
    private readonly ManageDataContext _context;
    public UserInfoController(ManageDataContext context)
    {
        _context = context;
    }

    /// <summary>
    /// 有參GET請求
    /// </summary>
    /// <param name="id">用戶編號id</param>
    /// <returns></returns>
    [HttpGet("{id}")]
    [ProducesResponseType(typeof(UserInfo), Status200OK)]
    [ProducesResponseType(typeof(string), Status404NotFound)]
    public async Task<ActionResult> Get(string id)
    {
        var res = await _context.UserInfos.FindAsync(id);
        if (res != null) return Ok(res);
        else return NotFound("Cannot find key.");
    }
}

啟動配置

Startup.cs註意一下順序的問題。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //實際測試,這個UseCors如果在UseAuthentication和UseAuthorization的後面,可能會導致vue.js訪問問題。
    app.UseCors("AllowAll");

    app.UseAuthentication();
    app.UseAuthorization();
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
    //使用AddNewtonsoftJson為了避免json的嚴格檢查。
    services.AddControllers().AddNewtonsoftJson();
    services.AddDbContext<ManageDataContext>();
    //後面還有不貼了
}

在ConfigureServices裡面,調用了AddNewtonsoftJson()。之所以沒有使用到預設的System.Text.Json,是因為它對客戶端上傳的信息要求太嚴格,如果是integer類型的值,上傳使用了string就不能正確識別對象,而Newtonsoft.Json沒有這個問題。

也可以修改System.Text.Json的預設行為,但是總是沒有那麼方便了。

調用方法

請求令牌

POST請求,api/token,設置header:Content-Type為application/json。body內容如下:

{
  "userName": "admin",
  "password": "123"
}

調用即可返回access_token與role。

調用被保護的API

需要設置header:

  • Authorization值為Bearer [獲取到的token]
  • Content-Type為application/json
    然後就可以自由調用自己有權訪問的API了。

總結

零零散散寫了這麼些,直接貼上代碼,項目是基於asp.net core 3.1與swagger的,本項目也可以作為一些小型項目的模板。

需要新建用戶的話,可以註釋掉[Authorize]或者我已經準備了一個用戶admin,密碼是123。
如果需要在windows上進行服務部署,可以參考我之前寫的TopShelf的文章

Github項目地址,歡迎Fork或者Star。

展望

  1. token刷新與吊銷。
  2. 註冊與手機/Email驗證。

參考資料

  1. 密碼哈希指南
  2. 加鹽哈希指南
  3. password-hashing

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前幾天一位朋友去面試,面試官問了他同步,非同步,多線程之間是什麼關係,非同步比同步高效在哪?多線程比單線程高效在哪?由於回答的不好,讓我幫他捋一下,其實回答這個問題不難,難就難在只對別人說理論,而沒有現殺的例子。 一:非同步 1. 到底解放了誰? 從基礎的同步說起 要說解放了誰,一定得有幾個參與者,舉個例 ...
  • 註冊/反註冊dll或ocx文件時,無論是用regsvr32還是DllRegisterServer/DllUnregisterServer,可能會遇到【記憶體位置訪問無效】的問題: 此時把操作系統的數據執行保護(Data Execution Prevention,下稱DEP)徹底關掉,重啟,應該就能解決 ...
  • 在 WPF 裡面有其他軟體完全比不上的超快速的觸摸,這個觸摸是通過 PenImc 獲取的。現在 WPF 開源了,本文就帶大家來閱讀觸摸底層的代碼,閱讀本文需要一點 C# 和 C++ 基礎 ...
  • 一、什麼是T4? 1.1、T4簡介 T4,即4個T開頭的英文字母組合:Text Template Transformation Toolkit,是微軟官方在Visual Studio 2008開始使用的代碼生成引擎。T4是由一些文本塊和控制邏輯組成的混合模板,簡單地說,T4可以根據模板生成您想要的文 ...
  • 1.首先,需要指定獲取的文件夾,以及獲取文件的文件名; 文件夾:strLocalPath = System.Windows.Forms.Application.StartupPath + "\\ExcelTemplate\\"; 文件名:temp.xlsx 2.代碼: ExecutionResult ...
  • 接上判斷當日為本周第幾天,接下來迴圈獲取周一至當前日對應的日期: for (int j = 0; j < DayinWeek; j++) { //星期對應Date string date = DateTime.Now.AddDays(1 - DayinWeek + j).ToString("yyyy ...
  • 直接上代碼: int DayinWeek = 0; string week = DateTime.Today.DayOfWeek.ToString(); #region 獲取DayinWeek值 switch (week) { case "Monday": DayinWeek = 1; break; ...
  • 最近想把網站的一個服務獨立出來專門提供數據用,交互用grpc,服務發現用consul,運行環境用docker 。 現在問題來了,首先,grpc傳輸使用http2協議,http2協議需要https,在內網情況下我們可能不想用https,那麼grpc也是可以使用http的,參考:Http2Unencry ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...