在現代Web應用程式中,通常會使用Web, WebApp, NativeApp等多種呈現方式,而後端也由以前的Razor渲染HTML,轉變為Stateless的RESTFulAPI,因此,我們需要一種標準的,通用的,無狀態的,與語言無關的認證方式,也就是本文要介紹的 JwtBearer 認證。 目錄 ...
在現代Web應用程式中,通常會使用Web, WebApp, NativeApp等多種呈現方式,而後端也由以前的Razor渲染HTML,轉變為Stateless的RESTFulAPI,因此,我們需要一種標準的,通用的,無狀態的,與語言無關的認證方式,也就是本文要介紹的JwtBearer認證。
目錄
Bearer認證
HTTP提供了一套標準的身份驗證框架:伺服器可以用來針對客戶端的請求發送質詢(challenge),客戶端根據質詢提供身份驗證憑證。質詢與應答的工作流程如下:伺服器端向客戶端返回401(Unauthorized,未授權)狀態碼,併在WWW-Authenticate頭中添加如何進行驗證的信息,其中至少包含有一種質詢方式。然後客戶端可以在請求中添加Authorization頭進行驗證,其Value為身份驗證的憑證信息。
在HTTP標準驗證方案中,我們比較熟悉的是"Basic"和"Digest",前者將用戶名密碼使用BASE64編碼後作為驗證憑證,後者是Basic的升級版,更加安全,因為Basic是明文傳輸密碼信息,而Digest是加密後傳輸。在前文介紹的Cookie認證屬於Form認證,並不屬於HTTP標準驗證。
本文要介紹的Bearer驗證也屬於HTTP協議標準驗證,它隨著OAuth協議而開始流行,詳細定義見: RFC 6570。
A security token with the property that any party in possession of the token (a "bearer") can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).
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
。
JWT(JSON WEB TOKEN)
上面介紹的Bearer認證,其核心便是BEARER_TOKEN,而最流行的Token編碼方式便是:JSON WEB TOKEN。
Json web token (JWT), 是為了在網路應用環境間傳遞聲明而執行的一種基於JSON的開放標準(RFC 7519)。該token被設計為緊湊且安全的,特別適用於分散式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
JWT是由.
分割的如下三部分組成:
頭部(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 認證的使用方式。
示例
模擬Token
ASP.NET Core 內置的JwtBearer驗證,並不包含Token的發放,我們先模擬一個簡單的實現:
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]UserDto userDto)
{
var user = _store.FindUser(userDto.UserName, userDto.Password);
if (user == null) return Unauthorized();
var tokenHandler = new JwtSecurityTokenHandler();
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.Name),
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber)
}),
Expires = expiresAt,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
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.Name,
auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds()
}
});
}
如上,使用微軟提供的Microsoft.IdentityModel.Tokens幫助類(源碼地址:azure-activedirectory-identitymodel-extensions-for-dotnet),可以很容易的創建出JwtToen,就不再多說。
註冊JwtBearer認證
首先添加JwtBearer
包引用:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 2.0.0
然後在Startup
類中添加如下配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
// 將下麵兩個參數設置為false,可以不驗證Issuer和Audience,但是不建議這樣做。
//ValidateIssuer = false, // 預設為true
//ValidateAudience = false, // 預設為true
ValidIssuer = "http://localhost:5200",
ValidAudience = "api",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Consts.Secret))
};
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
}
在JwtBearerOptions
的配置中,通常IssuerSigningKey(簽名秘鑰)
, ValidIssuer(Token頒發機構)
, ValidAudience(頒發給誰)
三個參數是必須的,後兩者用於與TokenClaims中的Issuer
和Audience
進行對比,不一致則驗證失敗(與上面發放Token中的Claims對應)。
而NameClaimType
和RoleClaimType
需與Token中的ClaimType一致,在IdentityServer中也是使用的JwtClaimTypes
,否則會造成User.Identity.Name
為空等問題。
添加受保護資源
創建一個需要授權的控制器,直接使用Authorize
即可:
[Authorize]
[Route("api/[controller]")]
public class SampleDataController : Controller
{
[HttpGet("[action]")]
public IEnumerable<WeatherForecast> WeatherForecasts()
{
return ...
}
}
運行
最後運行,直接訪問/api/SampleData/WeatherForecasts
,將返回一個401
:
HTTP/1.1 401 Unauthorized
Server: Kestrel
Content-Length: 0
WWW-Authenticate: Bearer
讓我們調用api/oauth/authenticate
,獲取一個JWT:
請求:
POST http://localhost:5200/api/oauth/authenticate HTTP/1.1
content-type: application/json
{
"username": "alice",
"password": "alice"
}
響應:
HTTP/1.1 200 OK
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.Y1TDz8KjLRh_vjQ_3iYP4oJw-fmhoboiAGPqIZ-ooNc","token_type":"Bearer","profile":{"sid":1,"name":"alice","auth_time":1509464340,"expires_at":1510069140}}
最後使用該Token,再次調用受保護資源:
GET http://localhost:5200/api/SampleData/WeatherForecasts HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.Y1TDz8KjLRh_vjQ_3iYP4oJw-fmhoboiAGPqIZ-ooNc
授權成功,返回了預期的數據:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[{"dateFormatted":"2017/11/3","temperatureC":35,"summary":"Chilly","temperatureF":94}]
擴展
自定義Token獲取方式
JwtBearer認證中,預設是通過Http的Authorization
頭來獲取的,這也是最推薦的做法,但是在某些場景下,我們可能會使用Url或者是Cookie來傳遞Token,那要怎麼來實現呢?
其實實現起來非常簡單,如前幾章介紹的一樣,JwtBearer也在認證的各個階段為我們提供了事件,來執行我們的自定義邏輯:
.AddJwtBearer(o =>
{
o.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
}
};
o.TokenValidationParameters = new TokenValidationParameters
{
...
};
然後在Url中添加access_token=[token]
,直接在瀏覽器中訪問:
同樣的,我們也可以很容易的在Cookie中讀取Token,就不再演示。
除了OnMessageReceived
外,還提供瞭如下幾個事件:
TokenValidated:在Token驗證通過後調用。
AuthenticationFailed: 認證失敗時調用。
Challenge: 未授權時調用。
使用OIDC服務
在上面的示例中,我們簡單模擬的Token頒發,功能非常簡單,並不適合在生產環境中使用,可是微軟也沒有提供OIDC服務的實現,好在.NET社區中提供了幾種實現,可供我們選擇:
Name | Description |
---|---|
AspNet.Security.OpenIdConnect.Server (ASOS) | Low-level/protocol-first OpenID Connect server framework for ASP.NET Core and OWIN/Katana |
IdentityServer4 | OpenID Connect and OAuth 2.0 framework for ASP.NET Core - officially certified by the OpenID Foundation and under governance of the .NET Foundation |
OpenIddict | Easy-to-use OpenID Connect server for ASP.NET Core |
PwdLess | Simple, stateless, passwordless authentication for ASP.NET Core |
我們在這裡使用IdentityServer4來搭建一個OIDC伺服器,並添加如下配置:
/********************OIDC伺服器代碼片段********************/
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// 配置IdentitryServer
services.AddIdentityServer()
.AddInMemoryPersistedGrants()
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers())
.AddDeveloperSigningCredential();
}
new Client
{
ClientId = "jwt.implicit",
ClientName = "Implicit Client (Web)",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:5200/callback" },
PostLogoutRedirectUris = { "http://localhost:5200/home" },
AllowedCorsOrigins = { "http://localhost:5200" },
AllowedScopes = { "openid", "profile", "email", "api" },
}
而JwtBearer客戶端的配置就更加簡單了,因為OIDC具有配置發現的功能:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = "https://oidc.faasx.com/";
o.Audience = "api";
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
}
如上,最重要的是Authority
參數,用來表示OIDC服務的地址,然後便可以自動發現Issuer
, IssuerSigningKey
等配置,而o.Audience
與o.TokenValidationParameters = new TokenValidationParameters { ValidAudience = "api" }
是等效的,後面分析源碼時會介紹。
OIDC相容OAuth2協議,我們可以使用上一章介紹的授權碼模式來獲取Token,也可以直接用戶名密碼模式來獲取Token:
請求:
POST https://oidc.faasx.com/connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id=client.rop&client_secret=secret&grant_type=password&scope=api&username=alice&password=alice
響應:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdlYzk5MjVlMmUzMTA2NmY2ZmU2ODgzMDRhZjU1ZmM0IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDk2NzI1NjksImV4cCI6MTUwOTY3NjE2OSwiaXNzIjoiaHR0cHM6Ly9vaWRjLmZhYXN4LmNvbSIsImF1ZCI6WyJodHRwczovL29pZGMuZmFhc3guY29tL3Jlc291cmNlcyIsImFwaSJdLCJjbGllbnRfaWQiOiJjbGllbnQucm9wIiwic3ViIjoiMDAxIiwiYXV0aF90aW1lIjoxNTA5NjcyNTY5LCJpZHAiOiJsb2NhbCIsIm5hbWUiOiJBbGljZSBTbWl0aCIsImVtYWlsIjoiQWxpY2VTbWl0aEBlbWFpbC5jb20iLCJzY29wZSI6WyJhcGkiXSwiYW1yIjpbInB3ZCJdfQ.PM93LThOZA3lkgPFVwieqGQQQtgmYDCY0oSFVmudv1hpKO6UaaZsmnn4ci9QjbGl5g2433JkDks5UIZsZ0xE62Qqq8PicPBBuaNoYrCf6dxR7j-0uZcoa7-FCKGu-0TrM8OL-NuMvN6_KEpbWa3jlkwibCK9YDIwJZilVoWUOrbbIEsKTa-DdLScmzHLUzksT8GBr0PAVhge9PRFiGqg8cgMLjsA62ZeDsR35f55BucSV5Pj0SAj26anYvrBNTHKOF7ze1DGW51Dbz6DRu1X7uEIxSzWiNi4cRVJ6Totjkwk5F78R9R38o_mYEdehZBjRHFe6zLd91hXcCKqOEh5eQ","expires_in":3600,"token_type":"Bearer"}
我在本章的示例代碼中,使用前端Angular框架演示瞭如何從本地登錄獲取Tokek或使用簡化模式(implicit)從OIDC伺服器獲取Token,然後保存到sesstionStorage,在發送請求時附加到請求頭中的示例,可供大家參考:JwtBearerSample。
源碼探索
JwtBearerPostConfigureOptions
在ASP.NET Core 2.0 Options框架中,新增了一種PostConfigure模式,用來在我們所註冊的Options配置執行完之後,再對Options做一些修改。
JwtBearerPostConfigureOptions用來實現配置發現:
public class JwtBearerPostConfigureOptions : IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string name, JwtBearerOptions options)
{
// 如果未設置options.TokenValidationParameters.ValidAudience,則使用options.Audience
if (string.IsNullOrEmpty(options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(options.Audience))
{
options.TokenValidationParameters.ValidAudience = options.Audience;
}
if (options.ConfigurationManager == null)
{
// 如果未設置MetadataAddress,則使用options.Authority+.well-known/openid-configuration
....
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata });
}
}
}
}
JwtBearerHandler
JwtBearerHandler相對於前幾章介紹的CookieHandler, OpenIdConnectHandler等,都簡單的多。
首先便是從請求中獲取Token:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
// 先觸發MessageReceived事件,來獲取Token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
token = messageReceivedContext.Token;
// Token為空時,從Authorization頭中獲取
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
...
}
然後初始化TokenValidationParameters
參數,為Token驗證做準備:
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.SigningKeys;
}
可以看到,從OIDC伺服器提供的配置發現中,獲取ValidIssuers
和IssuerSigningKeys
。
最後對Token進行驗證:
// Options.SecurityTokenValidators 預設為: new List<ISecurityTokenValidator> { new JwtSecurityTokenHandler() }
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
// RefreshOnIssuerKeyNotFound預設為True, 在SignatureKey未找到時,重新從OIDC伺服器獲取
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
continue;
}
...
// 觸發TokenValidated事件
await Events.TokenValidated(tokenValidatedContext);
// 預設為true,保存Token到`AuthenticationProperties`中,可以通過`context.AuthenticateAsync()`來獲取,在我們需要在服務端使用用戶Token調用其他資源是非常有用。
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
// 驗證成功
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
其核心的驗證也是在Microsoft.IdentityModel.Tokens
中,就不在深究。
當使用JwtBearer認證時,我們肯定不希望在未登錄時返回一個302
,因此在前面的示例中,我們配置了x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
,對應的,會執行JwtBearerHandler的HandleChallengeAsync
方法:
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var authResult = await HandleAuthenticateOnceSafeAsync();
var eventContext = new JwtBearerChallengeContext(Context, Scheme, Options, properties)
{
AuthenticateFailure = authResult?.Failure
};
if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
{
eventContext.Error = "invalid_token";
eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
}
await Events.Challenge(eventContext);
if (eventContext.Handled)
{
return;
}
Response.StatusCode = 401;
// 最終將相應報文拼接成如下:
// https://tools.ietf.org/html/rfc6750#section-3.1
// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
}
ASP.NET Core JwtBearer認證的完整源碼地址:Microsoft.AspNetCore.Authentication.JwtBearer。
總結
JwtToken其實與Cookie認證中加密後的Cookie值很像,他們都是基於Claim的,認證時無需STS(Security token service)的參與,這在分散式環境下提供了極大的便利。而他們的本質上的區別是:Cookie是微軟式的,很難與其他語言集成,而JwtToken則是開放再開放,與平臺,語言無關,在前端也可以直接解析出Claims。
PS: 在使用在Bearer認證時,通常還需與刷新Token配合來使用,因為JwtToken的驗證是無需經過STS的,而當用戶執行了退出,修改密碼等操作時,是無法使該Token失效的。所以,通常會給access_token
設置一個較短的有效期(JwtBearer認證預設會驗證有效期,通過notBefore
和expires
來驗證),當access_token
過期後,可以在用戶無感知的情況下,使用refresh_token
自動從STS重新獲取access_token
,但這就不屬於Bearer認證的範疇了,在後續介紹IdentityServer時再來詳細介紹一下。