ASP.NET Core使用JWT自定義角色并實(shí)現(xiàn)策略授權(quán)需要的接口
① 存儲(chǔ)角色/用戶(hù)所能訪(fǎng)問(wèn)的 API
例如
使用 List<ApiPermission> 存儲(chǔ)角色的授權(quán) API 列表。
可有可無(wú)。
可以把授權(quán)訪(fǎng)問(wèn)的 API 存放到 Token 中,Token 也可以只存放角色信息和用戶(hù)身份信息。
/// <summary>
/// API
/// </summary>
public class ApiPermission
{
/// <summary>
/// API名稱(chēng)
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// API地址
/// </summary>
public virtual string Url { get; set; }
}
② 實(shí)現(xiàn) IAuthorizationRequirement 接口
IAuthorizationRequirement 接口代表了用戶(hù)的身份信息,作為認(rèn)證校驗(yàn)、授權(quán)校驗(yàn)使用。
事實(shí)上,IAuthorizationRequirement 沒(méi)有任何要實(shí)現(xiàn)的內(nèi)容。
namespace Microsoft.AspNetCore.Authorization
{
//
// 摘要:
// Represents an authorization requirement.
public interface IAuthorizationRequirement
{
}
}
實(shí)現(xiàn) IAuthorizationRequirement ,可以任意定義需要的屬性,這些會(huì)作為自定義驗(yàn)證的便利手段。
要看如何使用,可以定義為全局標(biāo)識(shí),設(shè)置全局通用的數(shù)據(jù)。
我后面發(fā)現(xiàn)我這種寫(xiě)法不太好:
//IAuthorizationRequirement 是 Microsoft.AspNetCore.Authorization 接口
/// <summary>
/// 用戶(hù)認(rèn)證信息必要參數(shù)類(lèi)
/// </summary>
public class PermissionRequirement : IAuthorizationRequirement
{
/// <summary>
/// 用戶(hù)所屬角色
/// </summary>
public Role Roles { get; set; } = new Role();
public void SetRolesName(string roleName)
{
Roles.Name = roleName;
}
/// <summary>
/// 無(wú)權(quán)限時(shí)跳轉(zhuǎn)到此API
/// </summary>
public string DeniedAction { get; set; }
/// <summary>
/// 認(rèn)證授權(quán)類(lèi)型
/// </summary>
public string ClaimType { internal get; set; }
/// <summary>
/// 未授權(quán)時(shí)跳轉(zhuǎn)
/// </summary>
public string LoginPath { get; set; } = "/Account/Login";
/// <summary>
/// 發(fā)行人
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 訂閱人
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 過(guò)期時(shí)間
/// </summary>
public TimeSpan Expiration { get; set; }
/// <summary>
/// 頒發(fā)時(shí)間
/// </summary>
public long IssuedTime { get; set; }
/// <summary>
/// 簽名驗(yàn)證
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
/// <summary>
/// 構(gòu)造
/// </summary>
/// <param name="deniedAction">無(wú)權(quán)限時(shí)跳轉(zhuǎn)到此API</param>
/// <param name="userPermissions">用戶(hù)權(quán)限集合</param>
/// <param name="deniedAction">拒約請(qǐng)求的url</param>
/// <param name="permissions">權(quán)限集合</param>
/// <param name="claimType">聲明類(lèi)型</param>
/// <param name="issuer">發(fā)行人</param>
/// <param name="audience">訂閱人</param>
/// <param name="issusedTime">頒發(fā)時(shí)間</param>
/// <param name="signingCredentials">簽名驗(yàn)證實(shí)體</param>
public PermissionRequirement(string deniedAction, Role Role, string claimType, string issuer, string audience, SigningCredentials signingCredentials,long issusedTime, TimeSpan expiration)
{
ClaimType = claimType;
DeniedAction = deniedAction;
Roles = Role;
Issuer = issuer;
Audience = audience;
Expiration = expiration;
IssuedTime = issusedTime;
SigningCredentials = signingCredentials;
}
}
③ 實(shí)現(xiàn) TokenValidationParameters
Token 的信息配置
public static TokenValidationParameters GetTokenValidationParameters()
{
var tokenValida = new TokenValidationParameters
{
// 定義 Token 內(nèi)容
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AuthConfig.SecurityKey)),
ValidateIssuer = true,
ValidIssuer = AuthConfig.Issuer,
ValidateAudience = true,
ValidAudience = AuthConfig.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true
};
return tokenValida;
}
④ 生成 Token
用于將用戶(hù)的身份信息(Claims)和角色授權(quán)信息(PermissionRequirement)存放到 Token 中。
/// <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;
}
⑤ 實(shí)現(xiàn)服務(wù)注入和身份認(rèn)證配置
從別的變量導(dǎo)入配置信息,可有可無(wú)
// 設(shè)置用于加密 Token 的密鑰
// 配置角色權(quán)限
var roleRequirement = RolePermission.GetRoleRequirement(AccountHash.GetTokenSecurityKey());
// 定義如何生成用戶(hù)的 Token
var tokenValidationParameters = RolePermission.GetTokenValidationParameters();
配置 ASP.NET Core 的身份認(rèn)證服務(wù)
需要實(shí)現(xiàn)三個(gè)配置
- AddAuthorization 導(dǎo)入角色身份認(rèn)證策略
- AddAuthentication 身份認(rèn)證類(lèi)型
- AddJwtBearer Jwt 認(rèn)證配置
// 導(dǎo)入角色身份認(rèn)證策略
services.AddAuthorization(options =>
{
options.AddPolicy("Permission",
policy => policy.Requirements.Add(roleRequirement));
// ↓ 身份認(rèn)證類(lèi)型
}).AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
// ↓ Jwt 認(rèn)證配置
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
// 在安全令牌通過(guò)驗(yàn)證和ClaimsIdentity通過(guò)驗(yàn)證之后調(diào)用
// 如果用戶(hù)訪(fǎng)問(wèn)注銷(xiāo)頁(yè)面
OnTokenValidated = context =>
{
if (context.Request.Path.Value.ToString() == "/account/logout")
{
var token = ((context as TokenValidatedContext).SecurityToken as JwtSecurityToken).RawData;
}
return Task.CompletedTask;
}
};
});
注入自定義的授權(quán)服務(wù) PermissionHandler
注入自定義認(rèn)證模型類(lèi) roleRequirement
// 添加 httpcontext 攔截
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
services.AddSingleton(roleRequirement);
添加中間件
在微軟官網(wǎng)看到例子是這樣的。。。但是我測(cè)試發(fā)現(xiàn),客戶(hù)端攜帶了 Token 信息,請(qǐng)求通過(guò)驗(yàn)證上下文,還是失敗,這樣使用會(huì)返回403。
app.UseAuthentication();
app.UseAuthorization();
發(fā)現(xiàn)這樣才OK:
app.UseAuthorization();
app.UseAuthentication();
⑥ 實(shí)現(xiàn)登陸
可以在頒發(fā) Token 時(shí)把能夠使用的 API 存儲(chǔ)進(jìn)去,但是這種方法不適合 API 較多的情況。
可以存放 用戶(hù)信息(Claims)和角色信息,后臺(tái)通過(guò)角色信息獲取授權(quán)訪(fǎng)問(wèn)的 API 列表。
/// <summary>
/// 登陸
/// </summary>
/// <param name="username">用戶(hù)名</param>
/// <param name="password">密碼</param>
/// <returns>Token信息</returns>
[HttpPost("login")]
public JsonResult Login(string username, string password)
{
var user = UserModel.Users.FirstOrDefault(x => x.UserName == username && x.UserPossword == password);
if (user == null)
return new JsonResult(
new ResponseModel
{
Code = 0,
Message = "登陸失敗!"
});
// 配置用戶(hù)標(biāo)識(shí)
var userClaims = new Claim[]
{
new Claim(ClaimTypes.Name,user.UserName),
new Claim(ClaimTypes.Role,user.Role),
new Claim(ClaimTypes.Expiration,DateTime.Now.AddMinutes(_requirement.Expiration.TotalMinutes).ToString()),
};
_requirement.SetRolesName(user.Role);
// 生成用戶(hù)標(biāo)識(shí)
var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
identity.AddClaims(userClaims);
var token = JwtToken.BuildJwtToken(userClaims, _requirement);
return new JsonResult(
new ResponseModel
{
Code = 200,
Message = "登陸成功!請(qǐng)注意保存你的 Token 憑證!",
Data = token
});
}
⑦ 添加 API 授權(quán)策略
[Authorize(Policy = "Permission")]
⑧ 實(shí)現(xiàn)自定義授權(quán)校驗(yàn)
要實(shí)現(xiàn)自定義 API 角色/策略授權(quán),需要繼承 AuthorizationHandler<TRequirement>。
里面的內(nèi)容是完全自定義的, AuthorizationHandlerContext 是認(rèn)證授權(quán)的上下文,在此實(shí)現(xiàn)自定義的訪(fǎng)問(wèn)授權(quán)認(rèn)證。
也可以加上自動(dòng)刷新 Token 的功能。
/// <summary>
/// 驗(yàn)證用戶(hù)信息,進(jìn)行權(quán)限授權(quán)Handler
/// </summary>
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
List<PermissionRequirement> requirements = new List<PermissionRequirement>();
foreach (var item in context.Requirements)
{
requirements.Add((PermissionRequirement)item);
}
foreach (var item in requirements)
{
// 校驗(yàn) 頒發(fā)和接收對(duì)象
if (!(item.Issuer == AuthConfig.Issuer ?
item.Audience == AuthConfig.Audience ?
true : false : false))
{
context.Fail();
}
// 校驗(yàn)過(guò)期時(shí)間
var nowTime = DateTimeOffset.Now.ToUnixTimeSeconds();
var issued = item.IssuedTime +Convert.ToInt64(item.Expiration.TotalSeconds);
if (issued < nowTime)
context.Fail();
// 是否有訪(fǎng)問(wèn)此 API 的權(quán)限
var resource = ((Microsoft.AspNetCore.Routing.RouteEndpoint)context.Resource).RoutePattern;
var permissions = item.Roles.Permissions.ToList();
var apis = permissions.Any(x => x.Name.ToLower() == item.Roles.Name.ToLower() && x.Url.ToLower() == resource.RawText.ToLower());
if (!apis)
context.Fail();
context.Succeed(requirement);
// 無(wú)權(quán)限時(shí)跳轉(zhuǎn)到某個(gè)頁(yè)面
//var httpcontext = new HttpContextAccessor();
//httpcontext.HttpContext.Response.Redirect(item.DeniedAction);
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
⑨ 一些有用的代碼
將字符串生成哈希值,例如密碼。
為了安全,刪除字符串里面的特殊字符,例如 "、'、$。
public static class AccountHash
{
// 獲取字符串的哈希值
public static string GetByHashString(string str)
{
string hash = GetMd5Hash(str.Replace("\"", String.Empty)
.Replace("\'", String.Empty)
.Replace("$", String.Empty));
return hash;
}
/// <summary>
/// 獲取用于加密 Token 的密鑰
/// </summary>
/// <returns></returns>
public static SigningCredentials GetTokenSecurityKey()
{
var securityKey = new SigningCredentials(
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(AuthConfig.SecurityKey)), SecurityAlgorithms.HmacSha256);
return securityKey;
}
private static string GetMd5Hash(string source)
{
MD5 md5Hash = MD5.Create();
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(source));
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
return sBuilder.ToString();
}
}
簽發(fā) Token
PermissionRequirement 不是必須的,用來(lái)存放角色或策略認(rèn)證信息,Claims 應(yīng)該是必須的。
/// <summary>
/// 頒發(fā)用戶(hù)Token
/// </summary>
public class JwtToken
{
/// <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;
}
表示時(shí)間戳
// Unix 時(shí)間戳 DateTimeOffset.Now.ToUnixTimeSeconds(); // 檢驗(yàn) Token 是否過(guò)期 // 將 TimeSpan 轉(zhuǎn)為 Unix 時(shí)間戳 Convert.ToInt64(TimeSpan); DateTimeOffset.Now.ToUnixTimeSeconds() + Convert.ToInt64(TimeSpan);
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
VS2019中.NET如何實(shí)現(xiàn)打日志功能
本文主要介紹了VS2019中.NET如何實(shí)現(xiàn)打日志功能,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
.net下調(diào)用sqlserver存儲(chǔ)過(guò)程的小例子
2013-06-06
基于ABP框架實(shí)現(xiàn)數(shù)據(jù)字典開(kāi)發(fā)
本文詳細(xì)講解了基于ABP框架實(shí)現(xiàn)數(shù)據(jù)字典開(kāi)發(fā),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
Queryable.Union 方法實(shí)現(xiàn)json格式的字符串合并的具體實(shí)例
這篇文章介紹了Queryable.Union 方法實(shí)現(xiàn)json格式的字符串合并的具體實(shí)例,有需要的朋友可以參考一下2013-10-10
asp.net(c#)實(shí)現(xiàn)從sqlserver存取二進(jìn)制圖片的代碼
有一個(gè)員工表Employee,需要保存員工照片(Photo)到數(shù)據(jù)庫(kù)(sql server)上。員工照片對(duì)應(yīng)的字段是varbinary(max),也就是要存成二進(jìn)制文件類(lèi)型(這和以前討巧地存圖片文件路徑就不相同了),默認(rèn)可以為空。2011-09-09
發(fā)布asp.net core時(shí)如何修改ASPNETCORE_ENVIRONMENT環(huán)境變量
這篇文章主要介紹了發(fā)布asp.net core時(shí)如何修改ASPNETCORE_ENVIRONMENT環(huán)境變量,幫助大家更好的理解和學(xué)習(xí)使用.net技術(shù),感興趣的朋友可以了解下2021-04-04

