Nancy基於JwtBearer認證的使用與實現

簡單使用

第一步 , 用VS創建一個空的ASP.NET Core Web Application

558945-20170723085059653-1929113912.png

第二步 , 安裝相關的NuGet包

通過命令在Package Manager Console執行安裝下面的包,也可以用圖形界面來完成這一步操作。

Install-Package Microsoft.AspNetCore.Owin -Version 1.1.2 Install-Package Nancy -PreInstall-Package Nancy.Authentication.JwtBearer

其中,Microsoft.AspNetCore.Owin和Nancy是基礎包,Nancy.Authentication.JwtBearer是等下要用到的組件包。

第三步 , 修改Startup,添加對Nancy的支持。

public class Startup{        
    public void Configure(IApplicationBuilder app)    {            
        app.UseOwin(x=>x.UseNancy());
    }
}

第四步 , 添加一個Module來驗證Nancy是否可以正常使用

public class MainModule : NancyModule{    public MainModule()    {
        Get("/",_=> 
        {            return "test";
        });
    }
}

正常情況下,這個時候運行項目是OK的,大致效果如下:

558945-20170723085125778-1631421497.png

下面一步就是添加一個Bootstrapper用於啓用JwtBearer驗證。

public class DemoBootstrapper :  DefaultNancyBootstrapper{    protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)    {        base.ApplicationStartup(container, pipelines);        var keyByteArray = Encoding.ASCII.GetBytes("Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==");        var signingKey = new SymmetricSecurityKey(keyByteArray);        var tokenValidationParameters = new TokenValidationParameters
        {            // The signing key must match!
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = signingKey,            // Validate the JWT Issuer (iss) claim
            ValidateIssuer = true,
            ValidIssuer = "http://www.cnblogs.com/catcher1994",            // Validate the JWT Audience (aud) claim
            ValidateAudience = true,
            ValidAudience = "Catcher Wong",            // Validate the token expiry
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };        var configuration = new JwtBearerAuthenticationConfiguration
        {
            TokenValidationParameters = tokenValidationParameters
        };        
        //enable the JwtBearer authentication
        pipelines.EnableJwtBearerAuthentication(configuration);
    }
}

如果使用過Nancy項目自帶的其他認證方式(Basic,Forms和Stateless),就會發現下面的纔是關鍵,其他的只是用於JwtBearer認證的配置參數。

pipelines.EnableJwtBearerAuthentication(configuration);

下面簡單介紹一下配置參數。

配置參數主要有兩個,一個是TokenValidationParameters , 一個是Challenge 。

其中最主要的參數TokenValidationParameters,這是用來驗證客戶端傳過來的token是否合法的!

它位於Microsoft.IdentityModel.Tokens這個命名空間下面。

Challenge參數則是用於指定在Unauthorized時Http響應頭中WWW-Authenticate的值。它的默認值是Bearer

注:Challenge參數是從Microsoft.AspNetCore.Authentication.JwtBearer項目借鑑過來的。

到這裏, 我們已經完成了對JwtBearer認證的配置和啓用,下面還要驗證這個配置是否已經生效了!

創建一個新的Module,並在這個Module中使用RequiresAuthentication。

public class SecurityModule : NancyModule{    public SecurityModule() : base("/demo")    {        //important
        this.RequiresAuthentication();

        Get("/",_=> 
        {            return "JwtBearer authentication";
        });
    }    
}

注: 這裏需要引用Nancy.Security這個命名空間

到這裏,驗證的代碼也已經寫好了,當我們訪問 http://yourdomain.com/demo 的時候

瀏覽器會提示我們The requested resource requires user authentication , 並且在響應頭中我們可以看到WWW-Authenticate對應的值是Bearer。

558945-20170723085149465-478348754.png

我們創建一個合法的token值,然後通過Fiddler再發起一次請求,看看能否正常返回我們要的結果。

下面的代碼是生成一個測試token用的,其中的JwtSecurityToken對象應當與前面的配置一樣,才能確保token是有效的。

private string GetJwt(){    var now = DateTime.UtcNow;    var claims = new Claim[]
    {        new Claim(JwtRegisteredClaimNames.Sub, "demo"),        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),        new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
    };    
    //must the same as your setting in your boostrapper class
    var symmetricKeyAsBase64 = "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==";    var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);    var signingKey = new SymmetricSecurityKey(keyByteArray);    var jwt = new JwtSecurityToken(
        issuer: "http://www.cnblogs.com/catcher1994",
        audience: "Catcher Wong",
        claims: claims,
        notBefore: now,
        expires: now.Add(TimeSpan.FromMinutes(10)),
        signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));    var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);    var response = new
    {
        access_token = encodedJwt,
        expires_in = (int)TimeSpan.FromMinutes(10).TotalSeconds
    };    return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });
}

通過Fiddler執行這個帶上了token的請求,大致結果如下 :

558945-20170723085206450-2145261554.png

可以看到成功取到了相應的內容!

然後是本次測試用的token值相關的信息:

558945-20170723085222512-1906994960.png

注:用Fiddler發起請求的時候,記得要在請求頭部加上Authorization,它的值是Bearer+空格+token值

到這裏,已經展示瞭如何使用這個JwtBearer認證的組件。

下面就介紹一下是怎麼實現的這個組件!

如何實現

在繼續下面的內容之前,我假設大家對Nancy的Pipelines有所瞭解,如果不瞭解的可以參考我以前的下面的鏈接

  • Nancy之Pipelines三兄弟(Before After OnError)

  • Nancy官方的Wike頁面。

因爲其中的BeforePipeliine和AfterPipeline是實現這個認證組件的重要切入點。

另外,實現上還用了Nancy項目的代碼風格去編寫的代碼,所以你可能會發現與其自帶的Basic認證等寫法差不多。

從我們上面的例子使用來說明內部實現。

在上面例子的啓動器(Bootstrapper)中,我們有一行啓用JwtBearer認證的入口。這個入口是IPipelines的一個擴展方法。

/// <summary>/// Module requires JwtBearer authentication/// </summary>/// <param name="pipeline">Bootstrapper to enable</param>/// <param name="configuration">JwtBearer authentication configuration</param>public static void EnableJwtBearerAuthentication(this IPipelines pipeline, JwtBearerAuthenticationConfiguration configuration){
    JwtBearerAuthentication.Enable(pipeline, configuration);
}

在這個擴展方法中,調用了JwtBearerAuthentication這個靜態類的Enable方法,同時傳遞了當前的pipeline和JwtBearer認證的參數給這個方法。

下面是Enable方法的具體實現。

/// <summary>/// Enables JwtBearer authentication for the application/// </summary>/// <param name="pipelines">Pipelines to add handlers to (usually "this")</param>/// <param name="configuration">JwtBearer authentication configuration</param>public static void Enable(IPipelines pipelines, JwtBearerAuthenticationConfiguration configuration){    if (pipelines == null)
    {        throw new ArgumentNullException("pipelines");
    }    if (configuration == null)
    {        throw new ArgumentNullException("configuration");
    }

    pipelines.BeforeRequest.AddItemToStartOfPipeline(GetLoadAuthenticationHook(configuration));
    pipelines.AfterRequest.AddItemToEndOfPipeline(GetAuthenticationPromptHook(configuration));
}

以BeforeRequest爲例,我們把一個委託對象加入到了請求之前要處理的一個集合中去。這樣在每次請求之前都會去處理這個委託。

所以這裏有兩個部分。

  • 請求處理之前的token認證

  • 請求處理之後的響應

先來看看請求處理之前的token認證如何處理

private static Func<NancyContext, Response> GetLoadAuthenticationHook(JwtBearerAuthenticationConfiguration configuration)
{    return context => 
    {
        Validate(context,configuration);        return null;
    };
}

這裏也是一個空殼,用於返回AddItemToStartOfPipeline方法需要的委託對象。

真正處理token的還是Validate這個方法。認證的處理還藉助了System.IdentityModel.Tokens.Jwt命名空間下面的JwtSecurityTokenHandler類。

private static void Validate(NancyContext context, JwtBearerAuthenticationConfiguration configuration){            
    //get the token from request header
    var jwtToken = context.Request.Headers["Authorization"].FirstOrDefault() ?? string.Empty;   
    //whether the token value start with Bearer
    if (jwtToken.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
    {
        jwtToken = jwtToken.Substring("Bearer ".Length);
    }    else
    {                                
        return;
    }    
    //verify the token
    if (!string.IsNullOrWhiteSpace(jwtToken))
    {        try
        {
            SecurityToken validatedToken;            var tokenHandler = new JwtSecurityTokenHandler();            var validatedClaims = tokenHandler.ValidateToken(jwtToken, configuration.TokenValidationParameters, out validatedToken);            //var jwtSecurityToken = validatedToken as JwtSecurityToken;
            context.CurrentUser = validatedClaims;
        }        catch (Exception)
        {                                  
        }                               
    }
}

要對token進行驗證,首先要知道token是從那裏來的。常規情況下,都是將這個token放到請求頭的Authorization中。

所以第一步是要從請求頭中取出Authorization的值。這個值是必須以Bearer開頭的一個字符串。注意是Bearer加一個空格!

而我們要驗證的部分是去掉開頭這部分之後的內容。只需要構造一個JwtSecurityTokenHandler實例並調用這個實例的ValidateToken方法,並把要驗證的token值和我們的配置傳進去即可。

驗證成功後,最爲主要的一步是將ValidateToken方法的返回值賦給當前Nancy上下文的CurrentUser!!

當驗證失敗的時候,ValidateToken方法會拋出一個異常,這裏只catch了這個異常,並沒有進行其他額外的處理。要處理無非也就是記錄日記,可以在這裏trace一下,配合Diagnostics的使用。但是目前並沒有這樣做。

到這裏,Before已經OK了,現在要處理After了。

當然對於After,也是隻處理401(Unauthorized)的情況。主要是告訴客戶端 “當前請求的資源需要用戶認證”,並告訴客戶端當前請求的資源需要那種認證類型。

private static Action<NancyContext> GetAuthenticationPromptHook(JwtBearerAuthenticationConfiguration configuration){    return context =>
    {        if (context.Response.StatusCode == HttpStatusCode.Unauthorized)
        {            //add a response header 
            context.Response.WithHeader(JwtBearerDefaults.WWWAuthenticate, configuration.Challenge);
        }
    };
}

一個簡單的判斷加上響應頭部的處理。

到這裏,這個JwtBearer認證的組件已經ok了。

當然這裏只介紹了Pipeline的實現,還有一個是基於NancyModule的實現,本質還是pipeline的處理,所以這裏就不累贅了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章