欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-10 22:17:04
在.NET Core中想给API进行安全认证,最简单的无非就是Jwt,悠然记得一年前写的Jwt Demo,现在拿回来改成.NET Core的,但是在编码上的改变并不大,因为Jwt已经足够强大了。在项目中分为 DotNetCore_Jwt_Server 以及 DotNetCore_Jwt_Client ......

  在.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 类型的结果,其定义部分是这样的,我们就可以将计就计,给他来个连环套。

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

 连环套直接接受 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 中。

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