ASP.NET Core 2.2 : 二十六. 應用JWT進行用戶認證及Token的刷新

来源:https://www.cnblogs.com/FlyLolo/archive/2019/08/27/ASPNETCore2_26.html
-Advertisement-
Play Games

本文將通過實際的例子來演示如何在ASP.NET Core中應用JWT進行用戶認證以及Token的刷新方案(ASP.NET Core 系列目錄) 一、什麼是JWT? JWT(json web token)基於開放標準(RFC 7519),是一種無狀態的分散式的身份驗證方式,主要用於在網路應用環境間安全 ...


本文將通過實際的例子來演示如何在ASP.NET Core中應用JWT進行用戶認證以及Token的刷新方案(ASP.NET Core 系列目錄

一、什麼是JWT?

JWT(json web token)基於開放標準(RFC 7519),是一種無狀態的分散式的身份驗證方式,主要用於在網路應用環境間安全地傳遞聲明。它是基於JSON的,所以它也像json一樣可以在.Net、JAVA、JavaScript,、PHP等多種語言使用。
為什麼要使用JWT?
傳統的Web應用一般採用Cookies+Session來進行認證。但對於目前越來越多的App、小程式等應用來說,它們對應的服務端一般都是RestFul 類型的無狀態的API,再採用這樣的的認證方式就不是很方便了。而JWT這種無狀態的分散式的身份驗證方式恰好符合這樣的需求。

二、JWT的組成:

JWT是什麼樣子的呢?它就是下麵這樣的一段字元串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJuYmYiOjE1NjU5MjMxMjIsImV4cCI6MTU2NTkyMzI0MiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NDIxNCIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTQyMTUifQ.Mrta7nftmfXeo_igBVd4rl2keMmm0rg0WkqRXoVAeik
它是由三段“亂碼”字元串通過兩個“.”連接在一起組成。官網https://jwt.io/提供了它的驗證方式

它的三個字元串分別對應了上圖右側的Header、Payload和Signature三部分。

Header:

Header:
{
"alg": "HS256", 
"typ": "JWT"
}

標識加密方式為HS256,Token類型為JWT, 這段JSON通過Base64Url編碼形成上例的第一個字元串

Payload

Payload是JWT用於信息存儲部分,其中包含了許多種的聲明(claims)。
可以自定義多個聲明添加到Payload中,系統也提供了一些預設的類型
iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號

這部分通過Base64Url編碼生成第二個字元串。

Signature

Signature是用於Token的驗證。它的值類似這樣的表達式:Signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret),也就是說,它是通過將前兩個字元串加密後生成的一個新字元串。

所以只有擁有同樣加密密鑰的人,才能通過前兩個字元串獲得同樣的字元串,通過這種方式保證了Token的真實性。

三、認證流程

大概的流程是這樣的:

  1. 認證伺服器:用於用戶的登錄驗證和Token的發放。
  2. 應用伺服器:業務數據介面。被保護的API。
  3. 客戶端:一般為APP、小程式等。

認證流程:

  1.  用戶首先通過登錄,到認證伺服器獲取一個Token。
  2. 在訪問應用伺服器的API的時候,將獲取到的Token放置在請求的Header中。
  3. 應用伺服器驗證該Token,通過後返回對應的結果。

說明:這隻是示例方案,實際項目中可能有所不同。

  1. 對於小型項目,可能認證服務和應用服務在一起。本例通過分開的方式來實現,使我們能更好的瞭解二者之間的認證流程。
  2. 對於複雜一些的項目,可能存在多個應用服務,用戶獲取到的Token可以在多個分散式服務中被認證,這也是JWT的優勢之一。

 

關於JWT的文章很多,這裡就不做過多介紹了。下麵通過實際的例子來看一下 它是如何在ASP.NET Core 中應用的。

四、應用實例

上一節的圖:“JWT的認證流程”中涉及到客戶端、認證伺服器、應用伺服器三部分,下麵通過示例來對這三部分進行模擬:

  1. 認證伺服器:新建一個WebApi的解決方案,名為FlyLolo.JWT.Server。
  2. 應用伺服器:新建一個WebApi的解決方案,名為FlyLolo.JWT.API。
  3. 客戶端:這裡用Fiddler發送請求做測試。

認證服務

首先新建一個ASP.NET Core 的解決方案WebApi的解決方案 

將其命名為FlyLolo.JWT.Server。

首先新建一個TokenController用於登錄和Token的發放:

[Route("api/[controller]")]
public class TokenController : Controller
{
    private ITokenHelper tokenHelper = null;
    public TokenController(ITokenHelper _tokenHelper)
    {
        tokenHelper = _tokenHelper;
    }
    [HttpGet]
    public IActionResult Get(string code, string pwd)
    {
        User user = TemporaryData.GetUser(code);
        if (null != user && user.Password.Equals(pwd))
        {
            return Ok(tokenHelper.CreateToken(user));
        }
        return BadRequest();
    }
}

 它有個名為Get的Action用於接收提交的用戶名和密碼,併進行驗證,驗證通過後,調用TokenHelper的CreateToken方法生成Token返回。

這裡涉及到了User和TokenHelper兩個類。

User相關:

public class User
{
    public string Code { get; set; }
    public string Name { get; set; }
    public string Password { get; set; }
}

由於只是Demo,User類只含有以上三個欄位。在TemporaryData類中做了User的模擬數據

    /// <summary>
    /// 虛擬數據,模擬從資料庫或緩存中讀取用戶
    /// </summary>
    public static class TemporaryData
    {
        private static List<User> Users = new List<User>() { new User { Code = "001", Name = "張三", Password = "111111" }, new User { Code = "002", Name = "李四", Password = "222222" } };

        public static User GetUser(string code)
        {
            return Users.FirstOrDefault(m => m.Code.Equals(code));
        }
    }

這隻是模擬數據,實際項目中應該從資料庫或者緩存等讀取。

TokenHelper:

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

        public Token CreateToken(User user)
        {
            Claim[] claims = { new Claim(ClaimTypes.NameIdentifier,user.Code),new Claim(ClaimTypes.Name,user.Name) };

            return CreateToken(claims);
        }
        private Token CreateToken(Claim[] claims)
        {
            var now = DateTime.Now;var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes));
            var token = new JwtSecurityToken(
                issuer: _options.Value.Issuer,
                audience: _options.Value.Audience,
                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 };
        }
    }

    通過CreateToken方法創建Token,這裡有幾個關鍵參數:

  1. issuer            Token發佈者
  2. Audience      Token接受者
  3. expires          過期時間
  4. IssuerSigningKey  簽名秘鑰

對應的Token代碼如下:

    public class Token
    {
        public string TokenContent { get; set; }

        public DateTime Expires { get; set; }
    }

這樣通過TokenHelper的CreateToken方法生成了一個Token返回給了客戶端。到現在來看,貌似所有的工作已經完成了。並非如此,我們還需要在Startup文件中做一些設置。

public class Startup
{
// 。。。。。。此處省略部分代碼
public void ConfigureServices(IServiceCollection services) {
//讀取配置信息 services.AddSingleton
<ITokenHelper, TokenHelper>(); services.Configure<JWTConfig>(Configuration.GetSection("JWT")); //啟用JWT services.AddAuthentication(Options => { Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }). AddJwtBearer(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
//啟用認證中間件 app.UseAuthentication(); app.UseMvc(); } }

 這裡用到了配置信息,在appsettings.json中對認證信息做配置如下:

  "JWT": {
    "Issuer": "FlyLolo",
    "Audience": "TestAudience",
    "IssuerSigningKey": "FlyLolo1234567890",
    "AccessTokenExpiresMinutes": "30"
  }

 

運行這個項目,並通過Fidder以Get方式訪問api/token?code=002&pwd=222222,返回結果如下:

{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8
yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL
3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJuYmYiOjE1NjY3OTg0NzUsImV4cCI6MTU2NjgwMDI
3NSwiaXNzIjoiRmx5TG9sbyIsImF1ZCI6IlRlc3RBdWRpZW5jZSJ9.BVf3gOuW1E9RToqKy8XXp8uIvZKL-lBA-q9fB9QTEZ4
",
"expires":"2019-08-26T21:17:55.1183172+08:00"}

 

 客戶端登錄成功併成功返回了一個Token,認證服務創建完成

應用服務

新建一個WebApi的解決方案,名為FlyLolo.JWT.API。

添加BookController用作業務API。

[Route("api/[controller]")]
[Authorize]
public class BookController : Controller
{
    // GET: api/<controller>
    [HttpGet]
    [AllowAnonymous]
    public IEnumerable<string> Get()
    {
        return new string[] { "ASP", "C#" };
    }

    // POST api/<controller>
    [HttpPost]
    public JsonResult Post()
    {
        return new JsonResult("Create  Book ...");
    }
}

 對此Controller添加了[Authorize]標識,表示此Controller的Action被訪問時需要進行認證,而它的名為Get的Action被標識了[AllowAnonymous],表示此Action的訪問可以跳過認證。

在Startup文件中配置認證:

public class Startup
{
// 省略部分代碼
    public void ConfigureServices(IServiceCollection services)
    {
        #region 讀取配置
        JWTConfig config = new JWTConfig();
        Configuration.GetSection("JWT").Bind(config);
        #endregion

        #region 啟用JWT認證
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).
        AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidIssuer = config.Issuer,
                ValidAudience = config.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.IssuerSigningKey)),
                ClockSkew = TimeSpan.FromMinutes(1)
            };
        });
        #endregion

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseAuthentication();
        app.UseMvc();
    }
}

 這裡同樣用到了配置:

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

 appsettings.json:

  "JWT": {
    "Issuer": "FlyLolo",
    "Audience": "TestAudience",
    "IssuerSigningKey": "FlyLolo1234567890",
    "AccessTokenExpiresMinutes": "30"
  }

 關於JWT認證,這裡通過options.TokenValidationParameters對認證信息做了設置,ValidIssuer、ValidAudience、IssuerSigningKey這三個參數用於驗證Token生成的時候填寫的Issuer、Audience、IssuerSigningKey,所以值要和生成Token時的設置一致。

ClockSkew預設值為5分鐘,它是一個緩衝期,例如Token設置有效期為30分鐘,到了30分鐘的時候是不會過期的,會有這麼個緩衝時間,也就是35分鐘才會過期。為了方便測試(不想等太長時間),這裡我設置了1分鐘。

TokenValidationParameters還有一些其他參數,在它的構造方法中已經做了預設設置,代碼如下:

public TokenValidationParameters()
{
    RequireExpirationTime = true;  
    RequireSignedTokens = true;    
    SaveSigninToken = false;
    ValidateActor = false;
    ValidateAudience = true;  //是否驗證接受者
    ValidateIssuer = true;   //是否驗證發佈者
    ValidateIssuerSigningKey = false;  //是否驗證秘鑰
    ValidateLifetime = true; //是否驗證過期時間
    ValidateTokenReplay = false;
 }

 訪問api/book,正常返回了結果

["ASP","C#"]

 通過POST方式訪問,返回401錯誤。

這就需要使用獲取到的Toke了,如下圖方式再次訪問

添加了“Authorization: bearer Token內容”這樣的Header,可以正常訪問了。

至此,簡單的JWT認證示例就完成了,代碼地址https://github.com/FlyLolo/JWT.Demo/releases/tag/1.0

這裡可能會有個疑問,例如:

   1.Token被盜了怎麼辦?

    答: 在啟用Https的情況下,Token被放在Header中還是比較安全的。另外Token的有效期不要設置過長。例如可以設置為1小時(微信公眾號的網頁開發的Token有效期為2小時)。

   2. Token到期瞭如何處理?

   答:理論上Token過期應該是跳到登錄界面,但這樣太不友好了。可以在後臺根據Token的過期時間定期去請求新的Token。下一節來演示一下Token的刷新方案。

五、Token的刷新

   為了使客戶端能夠獲取到新的Token,對上文的例子進行改造,大概思路如下:

  1. 用戶登錄成功的時候,一次性給他兩個Token,分別為AccessToken和RefreshToken,AccessToken用於正常請求,也就是上例中原有的Token,RefreshToken作為刷新AccessToken的憑證。
  2. AccessToken的有效期較短,例如一小時,短一點安全一些。RefreshToken有效期可以設置長一些,例如一天、一周等。
  3. 當AccessToken即將過期的時候,例如提前5分鐘,客戶端利用RefreshToken請求指定的API獲取新的AccessToken並更新本地存儲中的AccessToken。

所以只需要修改FlyLolo.JWT.Server即可。

首先修改Token的返回方案,新增一個Model

    public class ComplexToken
    {
        public Token AccessToken { get; set; }
        public Token RefreshToken { get; set; }
    }

包含AccessToken和RefreshToken,用於用戶登錄成功後的Token結果返回。

修改 appsettings.json,添加兩個配置項:

    "RefreshTokenAudience": "RefreshTokenAudience", 
    "RefreshTokenExpiresMinutes": "10080" //60*24*7

 

RefreshTokenExpiresMinutes用於設置RefreshToken的過期時間,這裡設置了7天。RefreshTokenAudience用於設置RefreshToken的接受者,與原Audience值不一致,作用是使RefreshToken不能用於訪問應用服務的業務API,而AccessToken不能用於刷新Token。

修改TokenHelper:

    public enum TokenType
    {
        AccessToken = 1,
        RefreshToken = 2
    }
    public class TokenHelper : ITokenHelper
    {
        private IOptions<JWTConfig> _options;
        public TokenHelper(IOptions<JWTConfig> options)
        {
            _options = options;
        }

        public Token CreateAccessToken(User user)
        {
            Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name) };

            return CreateToken(claims, TokenType.AccessToken);
        }

        public ComplexToken CreateToken(User user)
        {
            Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name)
                //下麵兩個Claim用於測試在Token中存儲用戶的角色信息,對應測試在FlyLolo.JWT.API的兩個測試Controller的Put方法,若用不到可刪除
                , new Claim(ClaimTypes.Role, "TestPutBookRole"), new Claim(ClaimTypes.Role, "TestPutStudentRole")
            };

            return CreateToken(claims);
        }

        public ComplexToken CreateToken(Claim[] claims)
        {
            return new ComplexToken { AccessToken = CreateToken(claims, TokenType.AccessToken), RefreshToken = CreateToken(claims, 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 Token RefreshToken(ClaimsPrincipal claimsPrincipal)
        {
            var code = claimsPrincipal.Claims.FirstOrDefault(m => m.Type.Equals(ClaimTypes.NameIdentifier));
            if (null != code )
            {
                return CreateAccessToken(TemporaryData.GetUser(code.Value.ToString()));
            }
            else
            {
                return null;
            }
        }
    }

 

在登錄後,生成兩個Token返回給客戶端。在TokenHelper添加了一個RefreshToken方法,用於生成新的AccessToken。對應在TokenController中添加一個名為Post的Action,用於調用這個RefreshToken方法刷新Token

[HttpPost]
[Authorize]
public IActionResult Post()
{
    return Ok(tokenHelper.RefreshToken(Request.HttpContext.User));
}

這個方法添加了[Authorize]標識,說明調用它需要RefreshToken認證通過。既然啟用了認證,那麼在Startup文件中需要像上例的業務API一樣做JWT的認證配置。

        public void ConfigureServices(IServiceCollection services)
        {
            #region 讀取配置信息
            services.AddSingleton<ITokenHelper, TokenHelper>();
            services.Configure<JWTConfig>(Configuration.GetSection("JWT"));
            JWTConfig config = new JWTConfig();
            Configuration.GetSection("JWT").Bind(config);
            #endregion

            #region 啟用JWT
            services.AddAuthentication(Options =>
            {
                Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).
             AddJwtBearer(options =>
             {
                 options.TokenValidationParameters = new TokenValidationParameters
                 {
                     ValidIssuer = config.Issuer,
                     ValidAudience = config.RefreshTokenAudience,
                     IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.IssuerSigningKey))
                 };
             });
            #endregion

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

 

 註意這裡的ValidAudience被賦值為config.RefreshTokenAudience,和FlyLolo.JWT.API中的不一致,用於防止AccessToken和RefreshToken的混用。

再次訪問/api/token?code=002&pwd=222222,會返回兩個Token:

{"accessToken":{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8y
MDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUva
WRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW
1zL3JvbGUiOlsiVGVzdFB1dEJvb2tSb2xlIiwiVGVzdFB1dFN0dWRlbnRSb2xlIl0sIm5iZiI6MTU2NjgwNjQ3OSwiZXhwIjoxNTY2ODA4Mjc5LCJ
pc3MiOiJGbHlMb2xvIiwiYXVkIjoiVGVzdEF1ZGllbmNlIn0.wlMorS1V0xP0Fb2MDX7jI7zsgZbb2Do3u78BAkIIwGg
",
"expires":"2019-08-26T22:31:19.5312172+08:00"},

"refreshToken":{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8y
MDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUva
WRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW
1zL3JvbGUiOlsiVGVzdFB1dEJvb2tSb2xlIiwiVGVzdFB1dFN0dWRlbnRSb2xlIl0sIm5iZiI6MTU2NjgwNjQ3OSwiZXhwIjoxNTY3NDExMjc5LCJ
pc3MiOiJGbHlMb2xvIiwiYXVkIjoiUmVmcmVzaFRva2VuQXVkaWVuY2UifQ.3EDi6cQBqa39-ywq2EjFGiM8W2KY5l9QAOWaIDi8FnI
",
"expires":"2019-09-02T22:01:19.6143038+08:00"}}

 

 可以使用RefreshToken去請求新的AccessToken

 

測試用AccessToken可以正常訪問FlyLolo.JWT.API,用RefreshToken則不可以。

至此,Token的刷新功能改造完成。代碼地址:https://github.com/FlyLolo/JWT.Demo/releases/tag/1.1

疑問:RefreshToken有效期那麼長,被盜了怎麼辦,和直接將AccessToken的有效期延長有什麼區別?

個人認為:1. RefreshToken不像AccessToken那樣在大多數請求中都被使用。2. 應用類的API較多,對應的服務(器)也可能較多,所以泄露的概率更大一些。

 


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

-Advertisement-
Play Games
更多相關文章
  • 1. 簡介 當我們使用 或 迴圈來遍歷一個集合的元素, 允許我們不用擔心索引位置,甚至讓我們不僅僅是遍歷一個集合,同時還可以改變它。例如,你如果要刪除迴圈中的元素,那麼 迴圈不見得總是可行的。 結合自定義的迭代器,我們可以迭代更為複雜的對象,以及向前和向後移動,並且知曉如何利用其優勢也將變得非常清楚 ...
  • 一、基礎組件總結 1、文章閱讀目錄 1)、基礎組件 "Eureka組件,服務註冊與發現" "Ribbon和Feign組件,實現負載均衡" "Hystrix組件,實現服務熔斷" "Turbine組件,實現微服務集群監控" "Zuul組件,實現路由網關控制" "Config組件,實現配置統一管理" "Z ...
  • python_控制台輸出帶顏色的文字方法 在python開發的過程中,經常會遇到需要列印各種信息。海量的信息堆砌在控制臺中,就會導致信息都混在一起,降低了重要信息的可讀性。這時候,如果能給重要的信息加上字體顏色,那麼就會更加方便用戶閱讀了。 當然了,控制台的展示效果有限,並不能像前段一樣炫酷,只能做 ...
  • 什麼是Java中的引用? 引用的概念,如果一個變數的類型是 類類型,而非基本類型,那麼該變數又叫做引用。 步驟 1 : 引用和指向 new Hero(); 代表 創建 了一個Hero對象 但是也僅僅是創建了一個對象,沒有辦法訪問它 為了訪問這個對象,會使用 引用 來 代表 這個對象 Hero h = ...
  • 閱讀目錄 [TOC] 概覽 為了防止不符合規範的數據進入資料庫,在用戶對數據進行插入、修改、刪除等操作時,DBMS自動按照一定的約束條件對數據進行監測,使不符合規範的數據不能進入資料庫,以確保資料庫中存儲的數據正確、有效、相容。 約束條件與數據類型的寬度一樣,都是可選參數,主要分為以下幾種: "返回 ...
  • 閱讀目錄 數值類型 日期時間類型 字元串類型 ENUM和SET類型 "返回頂部" 數值類型 MySQL支持所有標準SQL數值數據類型。 這些類型包括嚴格數值數據類型(INTEGER、SMALLINT、DECIMAL和NUMERIC),以及近似數值數據類型(FLOAT、REAL和DOUBLE PREC ...
  • 主鍵就是一個表中每個數據行的唯一標識。不會有重覆值的列才能當主鍵。一個表可以沒有主鍵,但是會非常難以處理,因此沒有特殊理由表都要設定主鍵 主鍵有兩種選用策略:業務主鍵和邏輯主鍵。業務主鍵是使用有業務意義的欄位做主鍵,比如身份證號、銀行賬號等;邏輯主鍵是使用沒有任何業務意義的欄位做主鍵,完全給程式看的 ...
  • 目 錄 1. 概述... 2 2. 使命及目標... 3 3. 系統框架... 4 4. 設備容器(iNeuKernel)... 4 5. 第三方數據導入介面... 9 6. 視圖建模(iNeuView)... 10 7. 機器學習(iNeuAI)... 11 8. 徵集需求... 13 1. 概述 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...