ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口
目录
① 存储角色/用户所能访问的 api
例如
使用 list<apipermission>
存储角色的授权 api 列表。
可有可无。
可以把授权访问的 api 存放到 token 中,token 也可以只存放角色信息和用户身份信息。
/// <summary> /// api /// </summary> public class apipermission { /// <summary> /// api名称 /// </summary> public virtual string name { get; set; } /// <summary> /// api地址 /// </summary> public virtual string url { get; set; } }
② 实现 iauthorizationrequirement 接口
iauthorizationrequirement
接口代表了用户的身份信息,作为认证校验、授权校验使用。
事实上,iauthorizationrequirement
没有任何要实现的内容。
namespace microsoft.aspnetcore.authorization { // // 摘要: // represents an authorization requirement. public interface iauthorizationrequirement { } }
实现 iauthorizationrequirement
,可以任意定义需要的属性,这些会作为自定义验证的便利手段。
//iauthorizationrequirement 是 microsoft.aspnetcore.authorization 接口 /// <summary> /// 用户认证信息必要参数类 /// </summary> public class permissionrequirement : iauthorizationrequirement { /// <summary> /// 用户所属角色 /// </summary> public role roles { get; set; } = new role(); public void setrolesname(string rolename) { roles.name = rolename; } /// <summary> /// 无权限时跳转到此api /// </summary> public string deniedaction { get; set; } /// <summary> /// 认证授权类型 /// </summary> public string claimtype { internal get; set; } /// <summary> /// 未授权时跳转 /// </summary> public string loginpath { get; set; } = "/account/login"; /// <summary> /// 发行人 /// </summary> public string issuer { get; set; } /// <summary> /// 订阅人 /// </summary> public string audience { get; set; } /// <summary> /// 过期时间 /// </summary> public timespan expiration { get; set; } /// <summary> /// 颁发时间 /// </summary> public long issuedtime { get; set; } /// <summary> /// 签名验证 /// </summary> public signingcredentials signingcredentials { get; set; } /// <summary> /// 构造 /// </summary> /// <param name="deniedaction">无权限时跳转到此api</param> /// <param name="userpermissions">用户权限集合</param> /// <param name="deniedaction">拒约请求的url</param> /// <param name="permissions">权限集合</param> /// <param name="claimtype">声明类型</param> /// <param name="issuer">发行人</param> /// <param name="audience">订阅人</param> /// <param name="issusedtime">颁发时间</param> /// <param name="signingcredentials">签名验证实体</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; } }
③ 实现 tokenvalidationparameters
token 的信息配置
public static tokenvalidationparameters gettokenvalidationparameters() { var tokenvalida = new tokenvalidationparameters { // 定义 token 内容 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
用于将用户的身份信息(claims)和角色授权信息(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; }
⑤ 实现服务注入和身份认证配置
从别的变量导入配置信息,可有可无
// 设置用于加密 token 的密钥 // 配置角色权限 var rolerequirement = rolepermission.getrolerequirement(accounthash.gettokensecuritykey()); // 定义如何生成用户的 token var tokenvalidationparameters = rolepermission.gettokenvalidationparameters();
配置 asp.net core 的身份认证服务
需要实现三个配置
- addauthorization 导入角色身份认证策略
- addauthentication 身份认证类型
- addjwtbearer jwt 认证配置
// 导入角色身份认证策略 services.addauthorization(options => { options.addpolicy("permission", policy => policy.requirements.add(rolerequirement)); // ↓ 身份认证类型 }).addauthentication(options => { options.defaultauthenticatescheme = jwtbearerdefaults.authenticationscheme; options.defaultscheme = jwtbearerdefaults.authenticationscheme; options.defaultchallengescheme = jwtbearerdefaults.authenticationscheme; // ↓ jwt 认证配置 }) .addjwtbearer(options => { options.tokenvalidationparameters = tokenvalidationparameters; options.savetoken = true; options.events = new jwtbearerevents() { // 在安全令牌通过验证和claimsidentity通过验证之后调用 // 如果用户访问注销页面 ontokenvalidated = context => { if (context.request.path.value.tostring() == "/account/logout") { var token = ((context as tokenvalidatedcontext).securitytoken as jwtsecuritytoken).rawdata; } return task.completedtask; } }; });
注入自定义的授权服务 permissionhandler
注入自定义认证模型类 rolerequirement
// 添加 httpcontext 拦截 services.addsingleton<iauthorizationhandler, permissionhandler>(); services.addsingleton(rolerequirement);
添加中间件
貌似这两个不区分先后顺序
app.useauthorization(); app.useauthentication();
⑥ 实现登陆
可以在颁发 token 时把能够使用的 api 存储进去,但是这种方法不适合 api 较多的情况。
可以存放 用户信息(claims)和角色信息,后台通过角色信息获取授权访问的 api 列表。
/// <summary> /// 登陆 /// </summary> /// <param name="username">用户名</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 = "登陆失败!" }); // 配置用户标识 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); // 生成用户标识 var identity = new claimsidentity(jwtbearerdefaults.authenticationscheme); identity.addclaims(userclaims); var token = jwttoken.buildjwttoken(userclaims, _requirement); return new jsonresult( new responsemodel { code = 200, message = "登陆成功!请注意保存你的 token 凭证!", data = token }); }
⑦ 添加 api 授权策略
[authorize(policy = "permission")]
⑧ 实现自定义授权校验
要实现自定义 api 角色/策略授权,需要继承 authorizationhandler<trequirement>
。
里面的内容是完全自定义的, authorizationhandlercontext
是认证授权的上下文,在此实现自定义的访问授权认证。
也可以加上自动刷新 token 的功能。
/// <summary> /// 验证用户信息,进行权限授权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) { // 校验 颁发和接收对象 if (!(item.issuer == authconfig.issuer ? item.audience == authconfig.audience ? true : false : false)) { context.fail(); } // 校验过期时间 var nowtime = datetimeoffset.now.tounixtimeseconds(); var issued = item.issuedtime +convert.toint64(item.expiration.totalseconds); if (issued < nowtime) context.fail(); // 是否有访问此 api 的权限 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); // 无权限时跳转到某个页面 //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(); } }
签发 token
permissionrequirement
不是必须的,用来存放角色或策略认证信息,claims 应该是必须的。
/// <summary> /// 颁发用户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; }
表示时间戳
// unix 时间戳 datetimeoffset.now.tounixtimeseconds(); // 检验 token 是否过期 // 将 timespan 转为 unix 时间戳 convert.toint64(timespan); datetimeoffset.now.tounixtimeseconds() + convert.toint64(timespan);
上一篇: 东北黑木耳有什么特点,分几种类型呢