当前位置: 移动技术网 > IT编程>开发语言>.net > 在.NET Core中使用Jwt对API进行认证

在.NET Core中使用Jwt对API进行认证

2019年12月04日  | 移动技术网IT编程  | 我要评论

龙梅子的资料,天空乐队,小柠檬早教网

  在.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 中。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网