1. 程式人生 > >【翻譯】asp.net core2.0中的token認證

【翻譯】asp.net core2.0中的token認證

原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide

token認證在最近幾年正在成為一個流行的主題,特別是隨著移動應用和js應用不斷的獲得關注。像OAuth 2.0和OpenID Connect這樣的基於令牌的標準的廣泛採用,已經為令牌引入了更多的開發人員,但是最佳實踐並不總是清晰的。

我(作者)在asp.net croe 1.0的時候就開始使用,並且花費了相當長的時間在其中。asp.net core2.0在使用和校驗token方面提供了大量的支援,多虧有一個內建的JWT校驗中介軟體。然而,asp.net 4中的關於token生成的程式碼在asp.net core 2.0中卻不見蹤影。在asp.net core版本的早期,完整的token認證過程是一件令人困惑的事情。

現在asp.net core 2.1(2.2都發布了)已經穩定,那些令人困惑的事情也得到解決。在這篇文章中,我會檢查關於token認證的兩個方面的最佳實踐:token的校驗和token的生成。

什麼是token認證

令牌身份驗證是將令牌(有時稱為訪問令牌或承載令牌)附加到HTTP請求以進行身份驗證的過程。它通常與服務於移動或SPA (JavaScript)客戶端的API一起使用。

到達API的每一個請求都會被檢查。如果一個有效的token被發現了,這個請求就是被允許的,如果沒有token被發現或者這個token是無效的,那麼請求會被一個401未授權響應給拒絕掉。

token認證經常會在OAutho2.0和OpenID Connect這些協議中使用。如果你想看到這些協議是如何工作的,可以檢視

這裡

在ASP.NET CORE中校驗token

在asp.net core的api中新增token認證是一件非常容易的事情,這要多虧了JwtBearerAuthentication這個中介軟體(在框架中,內建的)。如果你正在消費一個由OpenID Connect伺服器建立的token,那麼關於配置的過程是非常容易的。

在你的Startup類中的ConfigurationServices方法中的任意地方配置你的中介軟體,並使用來自授權伺服器的值對其進行配置:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options 
=> { options.Authority = "{yourAuthorizationServerAddress}"; options.Audience = "{yourAudience}"; });

然後,在Configure方法中的UseMvc方法的上面,新增一句:

app.UseAuthentication();

第二步中UseAuthentication()是非常容易忘記的。如果經過身份驗證的呼叫不能正常工作,請確保在正確的位置添加了這一行(UseMvc的上面)。

JwtBearer中介軟體會在到達的請求的請求頭中查詢token(JSON Web Tokens,JWT)。如果一個有效的token被發現的話,請求就會被授權。然後你將[Authorize]特性新增到你想要保護的controller或者路由上:

[Route("/api/protected")
[Authorize]
public string Protected()
{
    return "Only if you have a valid token!";
}

你可能會感覺很奇怪:配置的服務中只有Aurhority和Audience這兩個引數被指定,JwtBearer中介軟體是如何校驗傳入的token的呢(這個token在request的Authorization header中)。

自動授權伺服器元資料

當JwtBearer中介軟體第一次處理到達的請求,他會試著從授權伺服器(也叫做authority或issuer,就是上面程式碼片段中配置的Authority)中檢查一些元資料。這些元資料,或者在OpenID Connect的術語中叫做發現文件(discovery document)的,包含了public key和其他需要校驗token的資訊。(如果你好奇元資料是什麼樣子的,這裡有一個OpenID Connect的發現文件,就是指的元資料。

如果JwtBearer中介軟體發現了這個元資料的文件,他(JwtBearer)會自動配置校驗(實際上是配置TokenValidationParameters這個型別的屬性),真幾把棒!

如果沒有發現這個元資料文件,你會得到一個錯誤:

System.IO.IOException: IDX10804: Unable to retrieve document from: "{yourAuthorizationServerAddress}".
System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).

如果你的授權伺服器沒有釋出這個元資料文件,或者你想要自己配置TokenValidationParameters,你可以在中介軟體的配置中手動的配置他們。

指定TokenValidationParameters

如果你想要細粒度的控制token的校驗過程,可以使用JwtBearer中提供的配置:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // Clock skew compensates for server time drift.
        // We recommend 5 minutes or less:
        ClockSkew = TimeSpan.FromMinutes(5),
        // Specify the key used to sign the token:
        IssuerSigningKey = signingKey,
        RequireSignedTokens = true,
        // Ensure the token hasn't expired:
        RequireExpirationTime = true,
        ValidateLifetime = true,
        // Ensure the token audience matches our audience value (default true):
        ValidateAudience = true,
        ValidAudience = "api://default",
        // Ensure the token was issued by a trusted authorization server (default true):
        ValidateIssuer = true,
        ValidIssuer = "https://{yourOktaDomain}/oauth2/default"
    };
});

TokenValidationParamters中最常用的選項是issuer、audience和clock skew。同時你需要提供提供token簽名的key(金鑰),這些key看起來會有所不同,取決於你使用的是對稱加密還是非對稱加密。

理解對稱簽名和非對稱簽名

授權伺服器生成的token會使用對稱或非對稱的簽名。如果你的授權伺服器釋出了一個元資料文件(discovery document),在文件中會包含關於金鑰的資訊(是一個public key),所有你通常不需要關心它是如何進行的。

然而,如果你要自己配置中介軟體,或者要手動的校驗token,你需要理解你的token是如何被簽名的。這兩種(對稱和非對稱)的簽名之間有什麼不同的地方?

對稱簽名/對稱key

一個對稱key,也叫做shared key或者shared secret,是一個在API和授權伺服器中同時儲存著的、保密的值(例如密碼)。並且授權伺服器頒發token。授權伺服器使用shared key對token進行簽名,同時API使用同樣的key對token進行校驗。

如果你有一個shared key,很容易在JwaBearer中介軟體中使用:

// For example only! Don't store your shared keys as strings in code.
// Use environment variables or the .NET Secret Manager instead.
var sharedKey = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes("mysupers3cr3tsharedkey!"));

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // Specify the key used to sign the token:
        IssuerSigningKey = sharedKey,
        RequireSignedTokens = true,
        // Other options...
    };
});

你要確保key被安全的儲存,向上面程式碼中那樣的儲存你的key不是一個好主意。應該將它儲存到環境變數或者使用.NET Secret ManagerASP.NET Core configuration model 會很容易的將這個工作完成:

var sharedKey = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Configuration["SigningKey"]);

同樣,不要在前端程式碼中儲存共享金鑰或將其公開給瀏覽器。它必須儲存在伺服器上。

非對稱簽名/非對稱key

使用非對稱簽名的話,你不需要在你的伺服器上面儲存一個金鑰(private key)。相反,一個public/private key(成對出現的)會被使用:授權伺服器使用private key對token進行簽名,並且釋出一個public key,public key用來校驗token。

通常情況下,public key的資訊會自動的從元資料文件(discovery document)中檢查和獲取(就像上面那個連結展示的discovery document)。如果你要手動的指定他,你需要從授權伺服器獲取關鍵引數並建立一個SecurityKey物件:

// Manually specify a public (asymmetric) key published as a JWK:
var publicJwk = new JsonWebKey
{
    KeyId = "(some key ID)",
    Alg = "RS256",
    E = "AQAB",
    N = "(a long string)",
    Kty = "RSA",
    Use = "sig"
};

多數場景下,public key可以從授權伺服器的JSON Web Key集(JWKS)中獲取到。授權伺服器可能會定期的旋轉(rotate)這個key,所以,你需要定期的檢查更新的key(從授權伺服器中)。但是如果你讓JwtBearer中介軟體自動的配置(通過discovery document),這一切都是自動進行的!

asp.net core中手動校驗token

在一些情況下,你可能想要不通過JwtBearer中介軟體來校驗token。使用這個中介軟體是首選,因為它可以很好的插入到asp.net core的授權系統中。

如果你真的想要手動的校驗JWT,你可以使用System.IdentityModel.Tokens.Jwt 包中的JwtSecurityTokenHandler。它使用同樣的TokenValidationParamters類來指定校驗選項:

private static JwtSecurityToken ValidateAndDecode(string jwt, IEnumerable<SecurityKey> signingKeys)
{
    var validationParameters = new TokenValidationParameters
    {
        // Clock skew compensates for server time drift.
        // We recommend 5 minutes or less:
        ClockSkew = TimeSpan.FromMinutes(5),
        // Specify the key used to sign the token:
        IssuerSigningKeys = signingKeys,
        RequireSignedTokens = true,
        // Ensure the token hasn't expired:
        RequireExpirationTime = true,
        ValidateLifetime = true,
        // Ensure the token audience matches our audience value (default true):
        ValidateAudience = true,
        ValidAudience = "api://default",
        // Ensure the token was issued by a trusted authorization server (default true):
        ValidateIssuer = true,
        ValidIssuer = "https://{yourOktaDomain}/oauth2/default"
    };

    try
    {
        var claimsPrincipal = new JwtSecurityTokenHandler()
            .ValidateToken(jwt, validationParameters, out var rawValidatedToken);

        return (JwtSecurityToken)rawValidatedToken;
        // Or, you can return the ClaimsPrincipal
        // (which has the JWT properties automatically mapped to .NET claims)
    }
    catch (SecurityTokenValidationException stvex)
    {
        // The token failed validation!
        // TODO: Log it or display an error.
        throw new Exception($"Token failed validation: {stvex.Message}");
    }
    catch (ArgumentException argex)
    {
        // The token was not well-formed or was invalid for some other reason.
        // TODO: Log it or display an error.
        throw new Exception($"Token was invalid: {argex.Message}");
    }
}

如果你的授權伺服器釋出了一個元資料文件,你可以通過 Microsoft.IdentityModel.Protocols.OpenIdConnect 包中的OpenIdConnectConfigurationRetriever類來對這個文件中的內容進行檢索,這個過程會自動的獲取簽名key:

var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
    // .well-known/oauth-authorization-server or .well-known/openid-configuration
    "{yourAuthorizationServerAddress}/.well-known/openid-configuration",
    new OpenIdConnectConfigurationRetriever(),
    new HttpDocumentRetriever());

var discoveryDocument = await configurationManager.GetConfigurationAsync();
var signingKeys = discoveryDocument.SigningKeys;

它負責令牌身份驗證的驗證端,但是如何生成令牌本身呢?

asp.net core中生成用於認證的token

回首asp.net 4.5的那段日子,UseOAuthAuthorizationServer中介軟體給你帶來一個可以輕易生成token的端點。然後,asp.net core團隊決定不要引入這個元件( decided not to bring it to ASP.NET Core,),這意味著你需要其他的東西來達成這個功能。也就是說你需要找到一個或者自己構建一個能夠生成token的授權伺服器。

通常有以下兩種方式:

① 使用一個雲服務像Azure AD B2C 或者 Okta

② 你自己構建或者配置一個

Hosted Authorization Server with Okta

用一個宿主授權伺服器來生成token是最簡單的。通過在上面註冊一個賬號然後根據網站的提示一步一步個進行下去之後,在你自己的應用中配置是非常簡單的:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.Authority = "https://{yourOktaDomain}/oauth2/default";
    options.Audience = "api://default";
});

還有其他的一些:

OpenIddict

OpenIddict is an easy-to-configure authorization server that works nicely with ASP.NET Core Identity and Entity Framework Core. It plugs right into the ASP.NET Core middleware pipeline and is easy to configure.

OpenIddict is a great choice if you’re already using ASP.NET Core Identity and want to generate tokens for your users. You can follow Mike Rousos’ in-depth tutorial on the MSDN blog to set it up and configure it in your application.

IdentityServer4

Thinktecture’s open-source IdentityServer project has been around for a long time, and it got a major update for .NET Core with IdentityServer4. Of the three packages discussed here, it’s the most powerful and flexible.

IdentityServer is a good choice when you want to roll your own full-fledged OpenID Connect authorization server that can handle complex use cases like federation and single sign-on. Depending on your use case, configuring IdentityServer4 can be a little complicated. Fortunately, the official documentation covers many common scenarios.

Token Authentication Can Be Complex!

I hope this article helps it feel a little less confusing. The ASP.NET Core team has done a great job of making it easy to add token authentication to your ASP.NET Core API, and options like OpenIddict and Okta make it easy to spin up an authorization server that generates tokens for your clients.

Here are some more resources if you want to keep learning: