在.NET Core中使用Jwt对API进行认证
在.net core中想给api进行安全认证,最简单的无非就是jwt,悠然记得一年前写的jwt demo,现在拿回来改成.net core的,但是在编码上的改变并不大,因为jwt已经足够强大了。在项目中分为 dotnetcore_jwt_server 以及 dotnetcore_jwt_client ,从名字就可以看出来是啥意思,博客园高手云集,我就不多诉说,这篇博客就当是一篇记录。
当然本案例是server&client双项目,如果你要合成自己发证的形式,那你就自己改下代码玩。
在server层都会有分发token的服务,在其中做了用户密码判断,随后根据 claim 生成 jwttoken 的操作。
其生成token的服务代码:
namespace dotnetcore_jwt_server.services { public interface itokenservice { string gettoken(user user); } public class tokenservice : itokenservice { private readonly jwtsetting _jwtsetting; public tokenservice(ioptions<jwtsetting> option) { _jwtsetting = option.value; } public string gettoken(user user) { //创建用户身份标识,可按需要添加更多信息 var claims = new claim[] { new claim(jwtregisteredclaimnames.jti, guid.newguid().tostring()), new claim("id", user.id.tostring(), claimvaluetypes.integer32), new claim("name", user.name), new claim("admin", user.isadmin.tostring(),claimvaluetypes.boolean) }; //创建令牌 var token = new jwtsecuritytoken( issuer: _jwtsetting.issuer, audience: _jwtsetting.audience, signingcredentials: _jwtsetting.credentials, claims: claims, notbefore: datetime.now, expires: datetime.now.addseconds(_jwtsetting.expireseconds) ); string jwttoken = new jwtsecuritytokenhandler().writetoken(token); return jwttoken; } } }
在获取token中我们依赖注入服务到控制器中,随后依赖它进行认证并且分发token,
public class valuescontroller : controllerbase { private readonly iuserservice _userservice; private readonly itokenservice _tokenservice; public valuescontroller(iuserservice userservice, itokenservice tokenservice) { _userservice = userservice; _tokenservice = tokenservice; } [httpget] public async task<string> get() { await task.completedtask; return "welcome the json web token solucation!"; } [httpget("gettoken")] public async task<string> gettokenasync(string name, string password) { var user = await _userservice.loginasync(name, password); if (user == null) return "login failed"; var token = _tokenservice.gettoken(user); var response = new { status = true, token = token, type = "bearer" }; return jsonconvert.serializeobject(response); } }
随后,我们又在项目配置文件中填写了几个字段,相关备注已注释,但值得说明的是有位朋友问我,服务器端生成的token不需要保存吗,比如redis或者是session,其实jwt token是无状态的,他们之间的对比第一个是你的token解密出来的信息正确与否,第二部则是看看你 securitykey 是否正确,就这样他们的认证才会得出结果。
"jwtsetting": { "securitykey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密钥 "issuer": "jwtissuertest", // 颁发者 "audience": "jwtaudiencetest", // 接收者 "expireseconds": 20000 // 过期时间 }
随后我们需要di两个接口以及初始化设置相关字段。
public void configureservices(iservicecollection services) { services.configure<jwtsetting>(configuration.getsection("jwtsetting")); services.addscoped<iuserservice, userservice>(); services.addscoped<itokenservice, tokenservice>(); services.addcontrollers(); }
在client中,我一般会创建一个中间件用于接受认证结果,aspnetcore jwt 源码中给我们提供了中间件,我们在进一步扩展,其源码定义如下:
/// <summary> /// extension methods to expose authentication on httpcontext. /// </summary> public static class authenticationhttpcontextextensions {/// <summary> /// extension method for authenticate. /// </summary> /// <param name="context">the <see cref="httpcontext"/> context.</param> /// <param name="scheme">the name of the authentication scheme.</param> /// <returns>the <see cref="authenticateresult"/>.</returns> public static task<authenticateresult> authenticateasync(this httpcontext context, string scheme) => context.requestservices.getrequiredservice<iauthenticationservice>().authenticateasync(context, scheme);
}
其该扩展会返回一个 authenticateresult 类型的结果,其定义部分是这样的,我们就可以将计就计,给他来个连环套。
连环套直接接受 httpcontext.authenticateasync(jwtbearerdefaults.authenticationscheme) 返回回来的值,随后进行判断返回相应的http响应码。
public class authmiddleware { private readonly requestdelegate _next; public authmiddleware(requestdelegate next) { _next = next; } public async task invoke(httpcontext httpcontext) { var result = await httpcontext.authenticateasync(jwtbearerdefaults.authenticationscheme); if (!result.succeeded) { httpcontext.response.statuscode = (int)httpstatuscode.unauthorized; await httpcontext.response.writeasync("authorize error"); } else { httpcontext.user = result.principal; await _next.invoke(httpcontext); } } }
当然你也得在client中添加认证的一些设置,它和server端的 issuersigningkey 一定要对应,否则认证失败。
public void configureservices(iservicecollection services) { services.addhttpcontextaccessor(); services.addscoped<iidentityservice, identityservice>(); var jwtsetting = new jwtsetting(); configuration.bind("jwtsetting", jwtsetting); services.addcors(options => { options.addpolicy("any", builder => { builder.allowanyorigin() //允许任何来源的主机访问 .allowanymethod() .allowanyheader(); }); }); services.addauthentication(jwtbearerdefaults.authenticationscheme) .addjwtbearer(options => { options.tokenvalidationparameters = new tokenvalidationparameters { validissuer = jwtsetting.issuer, validaudience = jwtsetting.audience, issuersigningkey = new symmetricsecuritykey(encoding.utf8.getbytes(jwtsetting.securitykey)), 默认 300s clockskew = timespan.zero }; }); services.addcontrollers(); }
随后,你就可以编写带需认证才可以访问的api了,如果认证失败则会返回401的错误响应。
[route("api/[controller]")] [apicontroller] public class valuescontroller : controllerbase { private readonly iidentityservice _identityservice; public valuescontroller(iidentityservice identityservice) { _identityservice = identityservice; } [httpget] [authorize] public async task<string> get() { await task.completedtask; return $"{_identityservice.getuserid()}:{_identityservice.getusername()}"; }
值得一提的是,我们可以根据 ihttpcontextaccessor 以来注入到我们的service或者api中,它是一个当前请求的认证信息上下文,这将有利于你获取用户信息去做该做的事情。
public class identityservice : iidentityservice { private readonly ihttpcontextaccessor _context; public identityservice(ihttpcontextaccessor context) { _context = context; } public int getuserid() { var nameid = _context.httpcontext.user.findfirst("id"); return nameid != null ? convert.toint32(nameid.value) : 0; } public string getusername() { return _context.httpcontext.user.findfirst("name")?.value; } }
在源码中该类的定义如下,实际上我们可以看到只不过是判断了当前的http上下文吧,所以我们得出,如果认证失败,上下本信息也是空的。
public class httpcontextaccessor : ihttpcontextaccessor { private static asynclocal<httpcontextholder> _httpcontextcurrent = new asynclocal<httpcontextholder>(); public httpcontext httpcontext { get { return _httpcontextcurrent.value?.context; } set { var holder = _httpcontextcurrent.value; if (holder != null) { // clear current httpcontext trapped in the asynclocals, as its done. holder.context = null; } if (value != null) { // use an object indirection to hold the httpcontext in the asynclocal, // so it can be cleared in all executioncontexts when its cleared. _httpcontextcurrent.value = new httpcontextholder { context = value }; } } } private class httpcontextholder { public httpcontext context; } }
如果要通过js来测试代码,您可以添加请求头来进行认证,beforesend是在请求之前的事件。
beforesend : function(request) { request.setrequestheader("authorization", sessionstorage.getitem("authorization")); }
好了,今天就说到这,代码地址在https://github.com/zaranetcore/dotnetcore_jwt 中。