1. 程式人生 > >ASP.NET Core 認證與授權[4]:JwtBearer認證

ASP.NET Core 認證與授權[4]:JwtBearer認證

在現代Web應用程式中,通常會使用Web, WebApp, NativeApp等多種呈現方式,而後端也由以前的Razor渲染HTML,轉變為Stateless的RESTFulAPI,因此,我們需要一種標準的,通用的,無狀態的,與語言無關的認證方式,也就是本文要介紹的JwtBearer認證。

目錄

Bearer認證

HTTP提供了一套標準的身份驗證框架:伺服器可以用來針對客戶端的請求傳送質詢(challenge),客戶端根據質詢提供身份驗證憑證。質詢與應答的工作流程如下:伺服器端向客戶端返回401(Unauthorized,未授權)狀態碼,並在WWW-Authenticate頭中新增如何進行驗證的資訊,其中至少包含有一種質詢方式。然後客戶端可以在請求中新增Authorization頭進行驗證,其Value為身份驗證的憑證資訊。

HTTPAuth

在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()
        }
    });
}

註冊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, 

            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
        };
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseAuthentication();
}

JwtBearerOptions的配置中,通常IssuerSigningKey(簽名祕鑰), ValidIssuer(Token頒發機構), ValidAudience(頒發給誰) 三個引數是必須的,後兩者用於與TokenClaims中的IssuerAudience進行對比,不一致則驗證失敗(與上面發放Token中的Claims對應)。

NameClaimTypeRoleClaimType需與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],直接在瀏覽器中訪問:

access_token_in_url

同樣的,我們也可以很容易的在Cookie中讀取Token,就不再演示。

除了OnMessageReceived外,還提供瞭如下幾個事件:

  • TokenValidated:在Token驗證通過後呼叫。

  • AuthenticationFailed: 認證失敗時呼叫。

  • Challenge: 未授權時呼叫。

使用OIDC服務

在上面的示例中,我們簡單模擬的Token頒發,功能非常簡單,並不適合在生產環境中使用,可是微軟也沒有提供OIDC服務的實現,好在.NET社群中提供了幾種實現,可供我們選擇:

Name Description
Low-level/protocol-first OpenID Connect server framework for ASP.NET Core and OWIN/Katana
OpenID Connect and OAuth 2.0 framework for ASP.NET Core - officially certified by the OpenID Foundation and under governance of the .NET Foundation
Easy-to-use OpenID Connect server for ASP.NET Core
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.Audienceo.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"}

我們使用https://jwt.io解析一下OIDC伺服器頒發的Token中的Claims:

{
  "nbf": 1509672569, // 2017/11/3 1:29:29 NotBefore Token生效時間,在此之前不可用
  "exp": 1509676169, // 2017/11/3 2:29:29 Expiration Token過期時間,在此之後不可用
  "iss": "https://oidc.faasx.com", // Issuer 頒發者,通常為STS伺服器地址
  "aud": [ // Audience Token的作用物件,也就是被訪問的資源伺服器授權標識
    "https://oidc.faasx.com/resources",
    "api"
  ],
  "client_id": "client.rop", // 客戶端標識
  "sub": "001",
  "auth_time": 1509672569, // Token頒發時間
  "idp": "local",
  "name": "Alice Smith",
  "email": "[email protected]",
  "scope": [
    "api"
  ],
  "amr": [
    "pwd"
  ]
}

我在本章的示例程式碼中,使用前端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伺服器提供的配置發現中,獲取ValidIssuersIssuerSigningKeys

最後對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"
}

總結

JwtToken其實與Cookie認證中加密後的Cookie值很像,他們都是基於Claim的,認證時無需STS(Security token service)的參與,這在分散式環境下提供了極大的便利。而他們的本質上的區別是:Cookie是微軟式的,很難與其他語言整合,而JwtToken則是開放再開放,與平臺,語言無關,在前端也可以直接解析出Claims。

PS: 在使用在Bearer認證時,通常還需與重新整理Token配合來使用,因為JwtToken的驗證是無需經過STS的,而當用戶執行了退出,修改密碼等操作時,是無法使該Token失效的。所以,通常會給access_token設定一個較短的有效期(JwtBearer認證預設會驗證有效期,通過notBeforeexpires來驗證),當access_token過期後,可以在使用者無感知的情況下,使用refresh_token自動從STS重新獲取access_token,但這就不屬於Bearer認證的範疇了,在後續介紹IdentityServer時再來詳細介紹一下。

相關推薦

ASP.NET Core 認證授權[4]:JwtBearer認證

在現代Web應用程式中,通常會使用Web, WebApp, NativeApp等多種呈現方式,而後端也由以前的Razor渲染HTML,轉變為Stateless的RESTFulAPI,因此,我們需要一種標準的,通用的,無狀態的,與語言無關的認證方式,也就是本文要介紹的JwtBearer認證。 目錄 Beare

ASP.NET Core 認證授權[1]:初識認證

在ASP.NET 4.X 中,我們最常用的是Forms認證,它既可以用於區域網環境,也可用於網際網路環境,有著非常廣泛的使用。但是它很難進行擴充套件,更無法與第三方認證整合,因此,在 ASP.NET Core 中對認證與授權進行了全新的設計,並使用基於宣告的認證(claims-based authentica

ASP.NET Core WebAPI中使用JWT Bearer認證授權

目錄 為什麼是 JWT Bearer 什麼是 JWT JWT 的優缺點 在 WebAPI 中使用 JWT 認證 重新整理 Token 使用授權 簡單授權 基於固定角色的授權 基於策略的授權 自定義策略授權 基於資源的授權 原

ASP.NET Core 中文文件 第二章 指南(4.1)ASP.NET Core MVC Visual Studio 入門

這篇教程將告訴你如何使用 Visual Studio 2015 構建一個 ASP.NET Core MVC Web 應用程式的基礎知識。 安裝 Visual Studio 和 .NET Core 安裝 Visual Studio Community 2015。選擇 Community 下載並執行預設安裝

ASP.NET Core 中jwt授權認證的流程原理

[TOC] ## 1,快速實現授權驗證 什麼是 JWT ?為什麼要用 JWT ?JWT 的組成? 這些百度可以直接找到,這裡不再贅述。 實際上,只需要知道 JWT 認證模式是使用一段 Token 作為認證依據的手段。 我們看一下 Postman 設定 Token 的位置。 ![](https://

Core中使用Hangfire 在Asp.Net Core中使用DI的方式使用Hangfire構建後臺執行指令碼 解決 ASP.NET Core Hangfire 未授權(401 Unauthorized)

    之前使用Quartz.Net,後來發現hangfire對Core的繼承更加的好,而且自帶管理後臺,這就比前者好用太多了。 安裝註冊 安裝 PM> Install-Package Hangfire Startup.cs,在ConfigureServices方法中添加註冊:

ASP.NET Core管道中介軟體

ASP.NET Core管道和ASP.NET的事件驅動的管道有很大的不同,現在你可以在Startup檔案的Configure方法中呼叫Use,UseWhen,Map,MapWhen,Run方法來為特定的請求增加特定的處理邏輯。可以實現防盜鏈,日誌,許可權認證,事務處理等。

ASP.NET Core 執行原理解剖[4]:進入HttpContext的世界

原文: ASP.NET Core 執行原理解剖[4]:進入HttpContext的世界 HttpContext是ASP.NET中的核心物件,每一個請求都會建立一個對應的HttpContext物件,我們的應用程式便是通過HttpContext物件來獲取請求資訊,最終生成響應,寫回到HttpContext中,完

ASP.NET Core SignalR 微信小程式互動

點選上方“程式設計師大咖”,選擇“置頂公眾號”關鍵時刻,第一時間送達!來源:James.Ying

Asp.Net Core SignalR 微信小程式互動筆記

什麼是Asp.Net Core SignalRAsp.Net Core SignalR 是微軟開

CZGL.Auth: ASP.NET Core Jwt角色授權快速配置庫

CZGL.Auth CZGL.Auth 是一個基於 Jwt 實現的快速角色授權庫,ASP.Net Core 的 Identity 預設的授權是 Cookie。而 Jwt 授權只提供了基礎實現和介面,需要自己實現角色授權和上下文攔截等。 使用第三方開源類庫,例如 IdentityServer4 ,過於複雜,學習

ASP.Net Core 3.1 中使用JWT認證

JWT認證簡單介紹     關於Jwt的介紹網上很多,此處不在贅述,我們主要看看jwt的結構。     JWT主要由三部分組成,如下: HEADER.PAYLOAD.SIGNATURE     HEADER包含token的元資料,主要是加密演算法,和簽名的型別,如下面的資訊,說明了 加密的物件型別是JWT,加

asp.net core 3.x 授權中的概念

前言 預計是通過三篇來將清楚asp.net core 3.x中的授權:1、基本概念介紹;2、asp.net core 3.x中授權的預設流程;3、擴充套件。 在完全沒有概念的情況下無論是看官方文件還是原始碼都暈乎乎的,希望本文能幫到你。不過我也是看原始碼結合官方文件看的,可能有些地方理解不對,所以只作為參考。

asp.net core 3.x 授權預設流程

一、前言 接上一篇《asp.net core 3.x 授權中的概念》,本篇看看asp.net core預設授權的流程。從兩個方面來看整個授權系統是怎麼執行的:啟動階段的配置、請求階段中介軟體的處理流程。 由於asp.net core 3.x目前使用終結點路由,因此授權框架可以用於所有asp.net web專案

Asp.Net Core 中IdentityServer4 授權中心之應用實戰

## 一、前言 查閱了大多數相關資料,查閱到的IdentityServer4 的相關文章大多是比較簡單並且多是翻譯官網的文件編寫的,我這裡在 Asp.Net Core 中IdentityServer4 的應用分析中會以一個電商系統架構升級過程中普遍會遇到的場景進行實戰性講述分析,同時最後會把我的實戰性的程式碼

Asp.Net Core 中IdentityServer4 授權中心之自定義授權模式

## 一、前言 上一篇我分享了一篇關於 [Asp.Net Core 中IdentityServer4 授權中心之應用實戰](https://www.cnblogs.com/jlion/p/12447081.html) 的文章,其中有不少博友給我提了問題,其中有一個博友問我的一個場景,我給他解答的還不夠完美,

Asp.Net Core 中IdentityServer4 授權原理及重新整理Token的應用

## 一、前言 上面分享了`IdentityServer4` 兩篇系列文章,核心主題主要是`密碼授權模式`及`自定義授權模式`,但是僅僅是分享了這兩種模式的使用,這篇文章進一步來分享`IdentityServer4`的授權流程及`refreshtoken`。 系列文章目錄(**沒看過的先看這幾篇文章再來閱

Asp.Net Core 3.1 學習4、Web Api 中基於JWT的token驗證及Swagger使用

1、初始JWT 1.1、JWT原理        JWT(JSON Web Token)是目前最流行的跨域身份驗證解決方案,他的優勢就在於伺服器不用存token便於分散式開發,給APP提供資料用於前後端分離的專案。登入產生的 token的專案完全可以獨立與其他專案。當用

ASP.NET Core管道詳解[4]: 中介軟體委託鏈

ASP.NET Core應用預設的請求處理管道是由註冊的IServer物件和HostingApplication物件組成的,後者利用一個在建立時提供的RequestDelegate物件來處理IServer物件分發給它的請求。而RequestDelegate物件實際上是由所有的中介軟體按照註冊順序建立的。換句話

ASP.NET Core ControllerIOC的羈絆

#### 前言     看到標題可能大家會有所疑問Controller和IOC能有啥羈絆,但是我還是拒絕當一個標題黨的。相信有很大一部分人已經知道了這麼一個結論,預設情況下ASP.NET Core的Controller並不會託管到IOC容器中,注意關鍵字我說的是"預設"