當前位置:
首頁 > 知識 > asp.net core 2.0 web api基於JWT自定義策略授權

asp.net core 2.0 web api基於JWT自定義策略授權

JWT(json web token)是一種基於json的身份驗證機制,流程如下:

asp.net core 2.0 web api基於JWT自定義策略授權

通過登錄,來獲取Token,再在之後每次請求的Header中追加Authorization為Token的憑據,服務端驗證通過即可能獲取想要訪問的資源。關於JWT的技術,可參考網路上文章,這裡不作詳細說明,

這篇博文,主要說明在asp.net core 2.0中,基於jwt的web api的許可權設置,即在asp.net core中怎麼用JWT,再次就是不同用戶或角色因為許可權問題,即使援用Token,也不能訪問不該訪問的資源。

基本思路是我們自定義一個策略,來驗證用戶,和驗證用戶授權,PermissionRequirement是驗證傳輸授權的參數。在Startup的ConfigureServices注入驗證(Authentication),授權(Authorization),和JWT(JwtBearer)

自定義策略:

已封閉成AuthorizeRolicy.JWT nuget包,並發布到nuget上:

https://www.nuget.org/packages/AuthorizePolicy.JWT/

源碼如下:

JwtToken.cs


/// <summary>

/// 獲取基於JWT的Token

/// </summary>

/// <param name="username"></param>

/// <returns></returns>

public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement)

{

var now = DateTime.UtcNow;

var jwt = new JwtSecurityToken(

issuer: permissionRequirement.Issuer,

audience: permissionRequirement.Audience,

claims: claims,

notBefore: now,

expires: now.Add(permissionRequirement.Expiration),

signingCredentials: permissionRequirement.SigningCredentials

);

var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

var response = new

{

Status = true,

access_token = encodedJwt,

expires_in = permissionRequirement.Expiration.TotalMilliseconds,

token_type = "Bearer"

};

return response;

}

Permission.cs


/// <summary>

/// 用戶或角色或其他憑據實體

/// </summary>

public class Permission

{

/// <summary>

/// 用戶或角色或其他憑據名稱

/// </summary>

public virtual string Name

{ get; set; }

/// <summary>

/// 請求Url

/// </summary>

public virtual string Url

{ get; set; }

}

PermissionRequirement.cs


/// <summary>

/// 必要參數類

/// </summary>

public class PermissionRequirement : IAuthorizationRequirement

{

/// <summary>

/// 用戶許可權集合

/// </summary>

public List<Permission> Permissions { get; private set; }

/// <summary>

/// 無許可權action

/// </summary>

public string DeniedAction { get; set; }

/// <summary>

/// 認證授權類型

/// </summary>

public string ClaimType { internal get; set; }

/// <summary>

/// 請求路徑

/// </summary>

public string LoginPath { get; set; } = "/Api/Login";

/// <summary>

/// 發行人

/// </summary>

public string Issuer { get; set; }

/// <summary>

/// 訂閱人

/// </summary>

public string Audience { get; set; }

/// <summary>

/// 過期時間

/// </summary>

public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5000);

/// <summary>

/// 簽名驗證

/// </summary>

public SigningCredentials SigningCredentials { get; set; }

/// <summary>

/// 構造

/// </summary>

/// <param name="deniedAction">無許可權action</param>

/// <param name="userPermissions">用戶許可權集合</param>

/// <summary>

/// 構造

/// </summary>

/// <param name="deniedAction">拒約請求的url</param>

/// <param name="permissions">許可權集合</param>

/// <param name="claimType">聲明類型</param>

/// <param name="issuer">發行人</param>

/// <param name="audience">訂閱人</param>

/// <param name="signingCredentials">簽名驗證實體</param>

public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials)

{

ClaimType = claimType;

DeniedAction = deniedAction;

Permissions = permissions;

Issuer = issuer;

Audience = audience;

SigningCredentials = signingCredentials;

}

}

自定義策略類PermissionHandler.cs


/// <summary>

/// 許可權授權Handler

/// </summary>

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>

{

/// <summary>

/// 驗證方案提供對象

/// </summary>

public IAuthenticationSchemeProvider Schemes { get; set; }

/// <summary>

/// 自定義策略參數

/// </summary>

public PermissionRequirement Requirement

{ get; set; }

/// <summary>

/// 構造

/// </summary>

/// <param name="schemes"></param>

public PermissionHandler(IAuthenticationSchemeProvider schemes)

{

Schemes = schemes;

}

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)

{

////賦值用戶許可權

Requirement = requirement;

//從AuthorizationHandlerContext轉成HttpContext,以便取出表求信息

var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;

//請求Url

var questUrl = httpContext.Request.Path.Value.ToLower();

//判斷請求是否停止

var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();

foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())

{

var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;

if (handler != null && await handler.HandleRequestAsync())

{

context.Fail();

return;

}

}

//判斷請求是否擁有憑據,即有沒有登錄

var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();

if (defaultAuthenticate != null)

{

var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);

//result?.Principal不為空即登錄成功

if (result?.Principal != null)

{

httpContext.User = result.Principal;

//許可權中是否存在請求的url

if (Requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0)

{

var name = httpContext.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType).Value;

//驗證許可權

if (Requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() <= 0)

{

//無許可權跳轉到拒絕頁面

httpContext.Response.Redirect(requirement.DeniedAction);

}

}

context.Succeed(requirement);

return;

}

}

//判斷沒有登錄時,是否訪問登錄的url,並且是Post請求,並助是form表單提交類型,否則為失敗

if (!questUrl.Equals(Requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST")

|| !httpContext.Request.HasFormContentType))

{

context.Fail();

return;

}

context.Succeed(requirement);

}

}

新建asp.net core 2.0的web api項目,並在項目添加AuthorizePolicy.JWT如圖

asp.net core 2.0 web api基於JWT自定義策略授權

先設置配置文件,用戶可以定義密匙和發生人,訂閱人

"Audience": {

"Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",

"Issuer": "gsw",

"Audience": "everone"

}

在ConfigureServices中注入驗證(Authentication),授權(Authorization),和JWT(JwtBearer)

Startup.cs


public void ConfigureServices(IServiceCollection services)

{

//讀取配置文件

var audienceConfig = Configuration.GetSection("Audience");

var symmetricKeyAsBase64 = audienceConfig["Secret"];

var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);

var signingKey = new SymmetricSecurityKey(keyByteArray);

var tokenValidationParameters = new TokenValidationParameters

{

ValidateIssuerSigningKey = true,

IssuerSigningKey = signingKey,

ValidateIssuer = true,

ValidIssuer = audienceConfig["Issuer"],

ValidateAudience = true,

ValidAudience = audienceConfig["Audience"],

ValidateLifetime = true,

ClockSkew = TimeSpan.Zero

};

var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);

services.AddAuthorization(options =>

{

//這個集合模擬用戶許可權表,可從資料庫中查詢出來

var permission = new List<Permission> {

new Permission { Url="/", Name="admin"},

new Permission { Url="/api/values", Name="admin"},

new Permission { Url="/", Name="system"},

new Permission { Url="/api/values1", Name="system"}

};

//如果第三個參數,是ClaimTypes.Role,上面集合的每個元素的Name為角色名稱,如果ClaimTypes.Name,即上面集合的每個元素的Name為用戶名

var permissionRequirement = new PermissionRequirement("/api/denied", permission, ClaimTypes.Role, audienceConfig["Issuer"], audienceConfig["Audience"], signingCredentials);

options.AddPolicy("Permission",

policy => policy.Requirements.Add(permissionRequirement));

}).AddAuthentication(options =>

{

options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;

options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

})

.AddJwtBearer(o =>

{

//不使用https

o.RequireHttpsMetadata = false;

o.TokenValidationParameters = tokenValidationParameters;

});

//注入授權Handler

services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

services.AddMvc();

}

在需要授的Controller上添加授權特性

[Authorize("Permission")]

PermissionController類有兩個方法,一個是登錄,驗證用戶名和密碼是否正確,如果正確就發放Token,如果失敗,驗證失敗,別一個成功登後的無許可權導航action。


[Authorize("Permission")]

public class PermissionController : Controller

{

/// <summary>

/// 自定義策略參數

/// </summary>

PermissionRequirement _requirement;

public PermissionController(IAuthorizationHandler authorizationHander)

{

_requirement = (authorizationHander as PermissionHandler).Requirement;

}

[AllowAnonymous]

[HttpPost("/api/login")]

public IActionResult Login(string username,string password,string role)

{

var isValidated = username == "gsw" && password == "111111";

if (!isValidated)

{

return new JsonResult(new

{

Status = false,

Message = "認證失敗"

});

}

else

{

//如果是基於角色的授權策略,這裡要添加用戶;如果是基於角色的授權策略,這裡要添加角色

var claims =new Claim[]{ new Claim(ClaimTypes.Name, username),new Claim(ClaimTypes.Role, role) };

//用戶標識

var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);

identity.AddClaims(claims);

//登錄

HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));

var token = JwtToken.BuildJwtToken(claims, _requirement);

return new JsonResult(token);

}

}

[AllowAnonymous]

[HttpGet("/api/denied")]

public IActionResult Denied()

{

return new JsonResult(new

{

Status = false,

Message = "你無許可權訪問"

});

}

}

下面定義一個控制台(.NetFramewrok)程序,用RestSharp來訪問我們定義的web api,其中1為admin角色登錄,2為system角色登錄,3為錯誤用戶密碼登錄,4是一個查詢功能,在startup.cs中,admin角色是具有查詢/api/values的許可權的,所以用admin登錄是能正常訪問的,用system登錄,能成功登錄,但沒有許可權訪問/api/values,用戶名密碼錯誤,訪問/aip/values,直接是沒有授權的


class Program

{

/// <summary>

/// 訪問Url

/// </summary>

static string _url = "http://localhost:39286";

static void Main(string[] args)

{

dynamic token = null;

while (true)

{

Console.WriteLine("1、登錄【admin】 2、登錄【system】 3、登錄【錯誤用戶名密碼】 4、查詢數據 ");

var mark = Console.ReadLine();

var stopwatch = new Stopwatch();

stopwatch.Start();

switch (mark)

{

case "1":

token = AdminLogin();

break;

case "2":

token = SystemLogin();

break;

case "3":

token = NullLogin();

break;

case "4":

AdminInvock(token);

break;

}

stopwatch.Stop();

TimeSpan timespan = stopwatch.Elapsed;

Console.WriteLine($"間隔時間:{timespan.TotalSeconds}");

}

}

static dynamic NullLogin()

{

var loginClient = new RestClient(_url);

var loginRequest = new RestRequest("/api/login", Method.POST);

loginRequest.AddParameter("username", "gswaa");

loginRequest.AddParameter("password", "111111");

//或用用戶名密碼查詢對應角色

loginRequest.AddParameter("role", "system");

IRestResponse loginResponse = loginClient.Execute(loginRequest);

var loginContent = loginResponse.Content;

Console.WriteLine(loginContent);

return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);

}

static dynamic SystemLogin()

{

var loginClient = new RestClient(_url);

var loginRequest = new RestRequest("/api/login", Method.POST);

loginRequest.AddParameter("username", "gsw");

loginRequest.AddParameter("password", "111111");

//或用用戶名密碼查詢對應角色

loginRequest.AddParameter("role", "system");

IRestResponse loginResponse = loginClient.Execute(loginRequest);

var loginContent = loginResponse.Content;

Console.WriteLine(loginContent);

return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);

}

static dynamic AdminLogin()

{

var loginClient = new RestClient(_url);

var loginRequest = new RestRequest("/api/login", Method.POST);

loginRequest.AddParameter("username", "gsw");

loginRequest.AddParameter("password", "111111");

//或用用戶名密碼查詢對應角色

loginRequest.AddParameter("role", "admin");

IRestResponse loginResponse = loginClient.Execute(loginRequest);

var loginContent = loginResponse.Content;

Console.WriteLine(loginContent);

return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);

}

static void AdminInvock(dynamic token)

{

var client = new RestClient(_url);

//這裡要在獲取的令牌字元串前加Bearer

string tk = "Bearer " + Convert.ToString(token?.access_token);

client.AddDefaultHeader("Authorization", tk);

var request = new RestRequest("/api/values", Method.GET);

IRestResponse response = client.Execute(request);

var content = response.Content;

Console.WriteLine($"狀態:{response.StatusCode} 返回結果:{content}");

}

}

運行結果:

asp.net core 2.0 web api基於JWT自定義策略授權



更多優質內容推薦:

2017優就業就業促進計劃:http://www.ujiuye.com/zt/jycj/?wt.bd=zdy35845tt

中公教育「勤工儉學計劃」,給你一個真正0元學習IT的機會!

http://www.ujiuye.com/zt/qgjx/?wt.bd=zdy35845tt

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 IT優就業 的精彩文章:

你該如何拯救,日復一日操心費力的自己?
程序員的十種形態,句句戳淚點
Kafka Streams 剖析
你總得叛逆兩次,一次是對父母,還有一次是對命運

TAG:IT優就業 |