主頁 > .NET開發 > 設計安全的API-JWT與OAuthor2

設計安全的API-JWT與OAuthor2

2020-09-20 20:47:50 .NET開發

最近新開發一個需要給App使用的API專案,開發API肯定會想到JASON Web Token(JWT)和OAuthor2(之前一篇隨筆記錄過OAuthor2),

JWT和OAuthor2的比較

  要像比較JWT和OAuthor2,首先要明白一點就是,這是兩個完全不同的東西,沒有可比性,

  JWT是一種認證協議

    官網:http://jwt.io

    JWT提供了一種用于發布介入靈擺(Access Token),并對發布的簽名介入令牌進行驗證的方法,令牌(Token)本身包含了一系列宣告,應用程式可以根據這些宣告限制用戶對資源的訪問,

    在新開發的API中,我選擇的是使用JWT,稍后會簡單介紹其在.net core中的使用,

  OAuthor2是一種授權框架

    OAuthor2是一種授權框架,提供了一套詳細的授權機制(指導),用戶或應用可以通過公開的或私有的設定,授權第三方應用訪問特定資源,

  既然JWT和OAuthor2沒有可比性,為什么還要把這兩個放在一起說呢?實際中,會有很多人拿JWT和OAuthor2作比較,或者分不清楚,很多情況下,在討論OAuthor2的實作時,會把JSON Web Token作為一種認證機制使用,這也是為什么他們會經常一起出現,

JSON Web Token(JWT)

  JWT是一種安全標準,基本思路就是用戶提供用戶名和密碼給認證服務器,服務器驗證用戶提交的資訊的合法性,如果認證成功,會產生并回傳一個Token(令牌),用戶可以使用這個token訪問服務器上受保護的資源,

一個token的例子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoibGl1dGFvIiwicm9sZSI6InNob3BVc2VycyIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InNob3BVc2VycyIsImFjdCI6IjEiLCJuYmYiOjE1NzQyNTAyMTgsImV4cCI6MTU3NTExNDIxOCwiaXNzIjoiWXVZdWUiLCJhdWQiOiJZdVl1ZSJ9.t39iwO-r_YgX5-7XyIV-by2duHfThqTQayI595VtqF

一個token包含三個部分:

header.claims.signature

為了安全的在url中使用,所有部分都base64 URL-safe進行編碼處理,

Header頭部分

  頭部分簡單宣告了型別(JWT)以及產生簽名所使用的的演算法,

{
  "alg" : "AES256",
  "typ" : "JWT"
}

Claims宣告

  宣告部分是整個token的核心,表示要發送的用戶詳細資訊,游學情況下,我們和有可能要在一個服務器上實作認證,然后訪問另一臺服務器上的資源,或者,通過單獨的介面來生成token,token被保存在應用程式客戶端(比如瀏覽器)使用,

  一個簡單的宣告(claim)的例子:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature簽名

  簽名的目的是為了保證上邊兩部分資訊不被篡改,如果嘗試使用Bas64對解碼后的token進行修改,簽名資訊就會失效,一般使用一個私鑰(private key)通過特定演算法對Header和Claims進行混淆產生簽名資訊,所以只有原始的token才能于簽名資訊匹配,
        這里有一個重要的實作細節,只有獲取了私鑰的應用程式(比如服務器端應用)才能完全認證token包含宣告資訊的合法性,所以,永遠不要把私鑰資訊放在客戶端(比如瀏覽器),

OAuthor2是什么?

  官網:http://oauth.net/2/

  相反,OAuthor2不是一個標準協議,而是一個安全的授權框架,它詳細描述了系統中不同角色、用戶、服務前端應用(比如API),以及客戶端(比如網站或移動APP)之間怎么實作相互認證,

OAuthor2的基本概念,可以去閱讀之前的一片隨筆,點擊此處

使用HTTPS保護用戶密碼

  在進一步討論OAuthor2和JWT的實作之前,有必要說一下,兩種方案都需要SSL安全保護,也就是對要傳輸的資料進行加密編碼,安全地傳輸用戶提供的私密資訊,在任何一個安全的系統里都是必要的,否則任何人都可以通過侵入私人wifi,在用戶登錄的時候竊取用戶的用戶名和密碼等資訊,

JWT和OAuthor2應該如何選擇

  在做選擇之前,參考一下下邊提到的幾點,

  1、時間投入

    OAuthor2是一個安全框架,描述了在各種不同場景下,多個應用之間的授權問題,有海量的資料需要學習,要完全理解需要花費大量時間,甚至對于一些有經驗的開發工程師來說,也會需要大概一個月的時間來深入理解OAuth2, 這是個很大的時間投入,相反,JWT是一個相對輕量級的概念,可能花一天時間深入學習一下標準規范,就可以很容易地開始具體實施,

  2、出現錯誤的風險

    OAuth2不像JWT一樣是一個嚴格的標準協議,因此在實施程序中更容易出錯,盡管有很多現有的庫,但是每個庫的成熟度也不盡相同,同樣很容易引入各種錯誤,在常用的庫中也很容易發現一些安全漏洞,當然,如果有相當成熟、強大的開發團隊來持續OAuth2實施和維護,可以一定成都上避免這些風險,

  3、社交登錄的好處

    在很多情況下,使用用戶在大型社交網站的已有賬戶來認證會方便,如果期望你的用戶可以直接使用Facebook或者Gmail之類的賬戶,使用現有的庫會方便得多,

JWT的使用場景

無狀態的分布式API

  JWT的主要優勢在于使用無狀態、可擴展的方式處理應用中的用戶會話,服務端可以通過內嵌的宣告資訊,很容易地獲取用戶的會話資訊,而不需要去訪問用戶或會話的資料庫,在一個分布式的面向服務的框架中,這一點非常有用,但是,如果系統中需要使用黑名單實作長期有效的token重繪機制,這種無狀態的優勢就不明顯了,

優勢:

  1、快速開發

  2、不需要cookie

  3、JSON在移動端的廣泛應用

  4、不依賴與社交登錄

  5、相對簡單的概念理解

限制

  1、token有長度限制

  2、token不能撤銷

  3、需要token有失效時間限制(exp)

OAuthor2使用場景

外包認證服務器

  上邊已經討論過,如果不介意API的使用依賴于外部的第三方認證提供者,你可以簡單地把認證作業留給認證服務商去做,也就是常見的,去認證服務商(比如facebook)那里注冊你的應用,然后設定需要訪問的用戶資訊,比如電子郵箱、姓名等,當用戶訪問站點的注冊頁面時,會看到連接到第三方提供商的入口,用戶點擊以后被重定向到對應的認證服務商網站,獲得用戶的授權后就可以訪問到需要的資訊,然后重定向回來,

優勢:

  1、快速開發

  2、實施代碼量小

  3、維護作業減少

大型企業解決方案

  如果設計的API要被不同的App使用,并且每個App使用的方式也不一樣,使用OAuth2是個不錯的選擇,考慮到作業量,可能需要單獨的團隊,針對各種應用開發完善、靈活的安全策略,當然需要的作業量也比較大!

優勢

  1、靈活的實作方式

  2、可以和JWT同時使用

  3、可以針對不同的應用擴展

簡單介紹下在.net core的專案中是如何使用JWT的,

首先,我們的服務是基于組件化的,當然需要先把身份認證的服務注冊進來,在Startup類中的ConfigureServices()方法中:

  services.AddSingleton<ITokenHelper, TokenHelper>();
  // configure strongly typed settings objects
  var jwtConfigSection = Configuration.GetSection("Authentication:JwtBearer");
  services.Configure<JWTConfig>(jwtConfigSection);

  // configure jwt authentication
  var jwtConfig = jwtConfigSection.Get<JWTConfig>();


services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddCookie(AdminUserAccountConst.AdminUserCookie, options => { options.Cookie.Name = AdminUserAccountConst.AdminUserCookieName; options.Cookie.HttpOnly = true; options.LoginPath = AdminUserAccountConst.AdminUserLoginPath; options.AccessDeniedPath = AdminUserAccountConst.AdminUserLoginPath; }).AddJwtBearer(AdminUserAccountConst.AdminUserJwt, o => { o.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role, ValidateLifetime = false, ValidIssuer = Configuration["Authentication:JwtBearer:Issuer"], ValidAudience = Configuration["Authentication:JwtBearer:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Authentication:JwtBearer:SecurityKey"])) }; o.ForwardChallenge = AdminUserAccountConst.AdminUserCookie; });

下面是上面所需要用到一些自定義型別:

AdminUserAccountConst

public class AdminUserAccountConst
{
    public const string AdminUserCookie = "AdminUserCookies";

    public const string AdminUserCookieName = "AdminUserCookieName";

    public const string AdminUserLoginPath = "/account/login";

    public const string AdminUserJwt = "AdminUserJwt";

    public const string AdminUserRole = "adminuser";
}
View Code

JWTConfig

public class JWTConfig
{
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public string IssuerSigningKey { get; set; }
    public int AccessTokenExpiresMinutes { get; set; }

    public string RefreshTokenAudience { get; set; }
    public int RefreshTokenExpiresMinutes { get; set; }
}
View Code

至于這些型別的欄位,可以自行在appsettings.json中去賦值,

"Authentication": {
  "JwtBearer": {
    "Issuer": "Bingle",
    "Audience": "Bingle",
    "IssuerSigningKey": "Bingle_C421AAEE0D114EAAACVD",
    "AccessTokenExpiresMinutes": "14400",

    "RefreshTokenAudience": "RefreshTokenAudience",
    "RefreshTokenExpiresMinutes": "43200" //60*24*30
  }
},
View Code

ITokenHelper與TokenHepler

 public interface ITokenHelper
 {
     ComplexToken CreateToken(User user);
     ComplexToken CreateToken(Claim[] claims);
     (Result result, string userCode) ConfirmRefreshToken(string refreshToken);
 }

 public class TokenHelper : ITokenHelper
 {
     private readonly IOptions<JWTConfig> _options;
     public TokenHelper(IOptions<JWTConfig> options)
     {
         _options = options;
     }

     public ComplexToken CreateToken(User user)
     {
         Claim[] claims = new Claim[]
         {
             new Claim(JwtClaimTypes.Id, user.UserCode),
             new Claim(JwtClaimTypes.Name, user.UserName),
             new Claim(JwtClaimTypes.Role, user.UserRole.GetExtendDescription()),
             new Claim(ClaimTypes.Role, user.UserRole.GetExtendDescription()),
             new Claim(JwtClaimTypes.Actor, user.PartyId)
         };
         return CreateToken(claims);
     }

     public ComplexToken CreateToken(Claim[] claims)
     {
         return new ComplexToken
         {
             AccessToken = CreateToken(claims, TokenType.AccessToken),
             RefreshToken = CreateToken(new Claim[]{claims.First(x=>x.Type == JwtClaimTypes.Id)}, TokenType.RefreshToken)
         };
     }

     /// <summary>
     /// 用于創建AccessToken和RefreshToken,
     /// 這里AccessToken和RefreshToken只是過期時間不同,【實際專案】中二者的claims內容可能會不同,
     /// 因為RefreshToken只是用于重繪AccessToken,其內容可以簡單一些,
     /// 而AccessToken可能會附加一些其他的Claim,
     /// </summary>
     /// <param name="claims"></param>
     /// <param name="tokenType"></param>
     /// <returns></returns>
     private Token CreateToken(Claim[] claims, TokenType tokenType)
     {
         var now = DateTime.Now;
         var expires = now.Add(TimeSpan.FromMinutes(tokenType.Equals(TokenType.AccessToken) ? _options.Value.AccessTokenExpiresMinutes : _options.Value.RefreshTokenExpiresMinutes));
         var token = new JwtSecurityToken(
             issuer: _options.Value.Issuer,
             audience: tokenType.Equals(TokenType.AccessToken) ? _options.Value.Audience : _options.Value.RefreshTokenAudience,
             claims: claims,
             notBefore: now,
             expires: expires,
             signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256));
         return new Token { TokenContent = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };
     }

     public (Result result, string userCode) ConfirmRefreshToken(string refreshToken)
     {
         var tokenHandler = new JwtSecurityTokenHandler();
         if (!tokenHandler.CanReadToken(refreshToken))
             return (Result.FromCode(ResultCode.InvalidToken, "RefreshToken不正確"), null);
         var jwtSecurityToken = tokenHandler.ReadJwtToken(refreshToken);
         if (jwtSecurityToken.Issuer != _options.Value.Issuer || !jwtSecurityToken.Audiences.Contains(_options.Value.RefreshTokenAudience))
             return (Result.FromCode(ResultCode.InvalidToken, "RefreshToken不正確"), null);
         if (jwtSecurityToken.ValidTo < DateTime.Now)
             return (Result.FromCode(ResultCode.InvalidToken, "RefreshToken已經過期了"), null);

         return (Result.Ok(), jwtSecurityToken.Claims.First(x => x.Type == JwtClaimTypes.Id).Value);
     }

 }
View Code

還要在Configure方法中使用中間件:

app.UseAuthentication();

首先,定義一個API的基類,后面的API繼承此基類就可以了

[Route("[controller]/[action]")]
[ApiController]
[Authorize(
    AuthenticationSchemes = AdminUserAccountConst.AdminUserCookie,
    Roles = AdminUserAccountConst.AdminUserRole)]
public class BasicAdminController : ControllerBase
{
}

現在新建一個用戶登錄和退出的APIController繼承與上面那個基類就可以了,這里簡化 了代碼

[HttpPost]
[AllowAnonymous]
[ProducesResponseType(typeof(Result<TokenResultDto>), 200)]
public JsonResult Login([FromBody]LoginDto model)
{
    var user = new User();//這里需要去資料庫中進行校驗
    if (user == null)
        return Json(new {IsSuccess=false,Msg="引數錯誤"});
    var result = _tokenHelper.CreateToken(new User
    {
        UserCode = user.UserCode,
        UserName = user.UserName,
        Telphone = user.Telphone,
        PartyId = user.ShopCode,
        UserRole = UserRoleEnum.user,
    });

    user.RefreshToken = result.RefreshToken.TokenContent;
    return Json(new TokenResultDto
    {
        AccessToken = result.AccessToken.TokenContent,
        Expires = result.AccessToken.Expires,
        RefreshToken = result.RefreshToken.TokenContent,
    });
}

這里使用AllowAnonymous標簽,是因為登錄并不需要進行身份驗證,當需要授權才能訪問的介面,不需要加上這個標簽,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/91617.html

標籤:.NET Core

上一篇:abp(net core)+easyui+efcore實作倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之一(二十七)

下一篇:.net core 反射的介紹與使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more