asp.net core 2.0 web api基於JWT自定義策略授權
JWT(json web token)是一種基於json的身份驗證機制,流程如下:
通過登錄,來獲取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如圖
先設置配置文件,用戶可以定義密匙和發生人,訂閱人
"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}");
}
}
運行結果:
更多優質內容推薦:
2017優就業就業促進計劃:http://www.ujiuye.com/zt/jycj/?wt.bd=zdy35845tt
中公教育「勤工儉學計劃」,給你一個真正0元學習IT的機會!
http://www.ujiuye.com/zt/qgjx/?wt.bd=zdy35845tt
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
※你該如何拯救,日復一日操心費力的自己?
※程序員的十種形態,句句戳淚點
※Kafka Streams 剖析
※你總得叛逆兩次,一次是對父母,還有一次是對命運
TAG:IT優就業 |