1、Bearer認證概念
Bearer驗證也屬於HTTP協議標准驗證。
Bearer驗證中的憑證稱為BEARER_TOKEN
,或者是access_token
,它的頒發和驗證完全由我們自己的應用程序來控制,而不依賴於系統和Web服務器,Bearer驗證的標准請求方式如下:
Authorization: Bearer [BEARER_TOKEN]
那么使用Bearer驗證的好處:
-
CORS: cookies + CORS 並不能跨不同的域名。而Bearer驗證在任何域名下都可以使用HTTP header頭部來傳輸用戶信息。
-
對移動端友好: 當你在一個原生平台(iOS, Android, WindowsPhone等)時,使用Cookie驗證並不是一個好主意,因為你得和Cookie容器打交道,而使用Bearer驗證則簡單的多。
-
CSRF: 因為Bearer驗證不再依賴於cookies, 也就避免了跨站請求攻擊。
-
標准:在Cookie認證中,用戶未登錄時,返回一個
302
到登錄頁面,這在非瀏覽器情況下很難處理,而Bearer驗證則返回的是標准的401 challenge
。
2、JWT(Json web token)
上面介紹的Bearer認證,其核心便是BEARER_TOKEN,而最流行的Token編碼方式便是:JSON WEB TOKEN。
頭部(Header)
Header 一般由兩個部分組成:
- alg
- typ
alg
是是所使用的hash算法,如:HMAC SHA256或RSA,typ
是Token的類型,在這里就是:JWT。
{
"alg": "HS256", "typ": "JWT" }
然后使用Base64Url編碼成第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<second part>.<third part>
載荷(Payload)
這一部分是JWT主要的信息存儲部分,其中包含了許多種的聲明(claims)。
Claims的實體一般包含用戶和一些元數據,這些claims分成三種類型:
-
reserved claims:預定義的 一些聲明,並不是強制的但是推薦,它們包括 iss (issuer), exp (expiration time), sub (subject),aud(audience) 等(這里都使用三個字母的原因是保證 JWT 的緊湊)。
-
public claims: 公有聲明,這個部分可以隨便定義,但是要注意和 IANA JSON Web Token 沖突。
-
private claims: 私有聲明,這個部分是共享被認定信息中自定義部分。
一個簡單的Pyload可以是這樣子的:
{
"sub": "1234567890", "name": "John Doe", "admin": true }
這部分同樣使用Base64Url編碼成第二部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.<third part>
簽名(Signature)
Signature是用來驗證發送者的JWT的同時也能確保在期間不被篡改。
在創建該部分時候你應該已經有了編碼后的Header和Payload,然后使用保存在服務端的秘鑰對其簽名,一個完整的JWT如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
因此使用JWT具有如下好處:
-
通用:因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
-
緊湊:JWT的構成非常簡單,字節占用很小,可以通過 GET、POST 等放在 HTTP 的 header 中,非常便於傳輸。
-
擴展:JWT是自我包涵的,包含了必要的所有信息,不需要在服務端保存會話信息, 非常易於應用的擴展。
關於更多JWT的介紹,網上非常多,這里就不再多做介紹。下面,演示一下 ASP.NET Core 中 JwtBearer 認證的使用方式。
DEMO
1、添加jwt包引用
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 2.0.0
2、Startup
類中添加如下配置
services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role, ValidIssuer = "http://localhost:5200", ValidAudience = "api", IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Consts.Secret)) /***********************************TokenValidationParameters的參數默認值***********************************/ // RequireSignedTokens = true, // SaveSigninToken = false, // ValidateActor = false, // 將下面兩個參數設置為false,可以不驗證Issuer和Audience,但是不建議這樣做。 // ValidateAudience = true, // ValidateIssuer = true, // ValidateIssuerSigningKey = false, // 是否要求Token的Claims中必須包含Expires // RequireExpirationTime = true, // 允許的服務器時間偏移量 // ClockSkew = TimeSpan.FromSeconds(300), // 是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比 // ValidateLifetime = true }; o.Events = new JwtBearerEvents() { OnMessageReceived = context => { //支持通過url傳token context.Token = context.Request.Query["access_token"]; return Task.CompletedTask; } }; }); public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWTDemo v1")); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
3、增加生成token的action
[HttpPost("authenticate")] public IActionResult Authenticate([FromBody] UserDto userDto) { //驗證 var user = _store.FindUser(userDto.UserName, userDto.Password); if (user == null) return Unauthorized(); //JWT載荷(Payload) var key = Encoding.ASCII.GetBytes(Consts.Secret); var authTime = DateTime.UtcNow; var expiresAt = authTime.AddDays(7); var tokenDescriptor = new SecurityTokenDescriptor { //內容 Subject = new ClaimsIdentity(new Claim[] { new Claim(JwtClaimTypes.Audience,"api"), new Claim(JwtClaimTypes.Issuer,"http://localhost:5200"), new Claim(JwtClaimTypes.Id, user.Id.ToString()), new Claim(JwtClaimTypes.Name, user.UserName), new Claim(JwtClaimTypes.Email, user.Email), new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber) }), //過期時間 Expires = expiresAt, //簽證 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); return Ok(new { access_token = tokenString, token_type = "Bearer", profile = new { sid = user.Id, name = user.UserName, auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(), expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds() } }); }
4、添加受保護資源
5、運行測試
5.1直接訪問WeatherForecast接口,會返回401。
5.2先訪問Authenticate?username=a&pwd=123獲取token.
5.3帶上token重新請求WeatherForecast,請求成功。
測試代碼地址:https://gitee.com/xiaoqingyao/authentication-netcore
源:https://www.cnblogs.com/RainingNight/p/jwtbearer-authentication-in-asp-net-core.html