zoukankan      html  css  js  c++  java
  • WebApi+Grpc+Jwt身份认证

    使用Grpc做服务间通信,使用JWT,JWT可以使用在前端,后端,微服务等。

    服务端:

    首先需要安装nuget包 Microsoft.AspNetCore.Authentication.JwtBearer

    首先创建JWTHelp.cs

    using DataService01.protos;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace DataService01.Models
    {
        public class JWTHelper
        {
            /// <summary>
            /// 创建token
            /// </summary>
            /// <param name="_users"></param>
            /// <param name="jwtDTO"></param>
            /// <returns></returns>
            public async Task<TokenModel> IssueJwt(users _users, JWTDTO jwtDTO)
            {
                var exp = $"{new DateTimeOffset(DateTime.Now.AddMinutes(jwtDTO.ExpireMinutes)).ToUnixTimeMilliseconds()}";
                List<Claim> claims = new List<Claim>() {
                new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds()}"),
                new Claim(JwtRegisteredClaimNames.Exp,exp),
                new Claim(JwtRegisteredClaimNames.Iss,jwtDTO.Issuer),
                new Claim(JwtRegisteredClaimNames.Aud,jwtDTO.Audience),
    
                new Claim(ClaimTypes.Name,_users.Name),
                new Claim(ClaimTypes.Role,_users.Roleid.ToString()),
                new Claim("loginname",_users.LoginName),
                new Claim("isman",_users.IsMan.ToString())
                            };
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtDTO.SecurityKey));
                var cerds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(issuer: jwtDTO.Issuer,audience:jwtDTO.Audience, claims: claims,expires:DateTime.Now.AddMinutes(jwtDTO.ExpireMinutes), signingCredentials: cerds);
                string jwt_token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
    
                // jwtSecurityToken.ValidTo;//过期时间
                return  new TokenModel {
                    Token = jwt_token,
                    ExpireTime = jwtSecurityToken.ValidTo,
                    Success = "ok"
                };
            }
            /// <summary>
            /// 解析jwt中的明文内容,不建议放关键信息
            /// </summary>
            /// <param name="jwtstr"></param>
            /// <returns></returns>
            public users SerializeJwt(string jwtstr)
            {
                var jwt_hender = new JwtSecurityTokenHandler();
                JwtSecurityToken securityToken = jwt_hender.ReadJwtToken(jwtstr);
                users _users = new users()
                {
                    //ID = Convert.ToInt32(securityToken.Id),
                    Name = securityToken.Payload[ClaimTypes.Name].ToString(),
                    LoginName = securityToken.Payload["loginname"].ToString(),
                    Roleid = Convert.ToInt32(securityToken.Payload[ClaimTypes.Role] ?? 0),
                    IsMan = Convert.ToBoolean(securityToken.Payload["isman"])
                };
                return _users;
            }
        }
        public class TokenModel
        {
           public string Token { get; set; }
            public DateTime ExpireTime { get; set; }
            public string Success { get; set; }
        }
    }
    JwtHelp.cs

    在服务端的Startup.cs中添加身份认证和授权

    首先在 ConfigureServices 方法中增加

    services.AddAuthorization(option => option.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
                {
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
                    policy.RequireClaim("sub");
                }));
                //services.AddAuthorization();
                services.AddAuthentication().AddJwtBearer(options=> {
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidIssuer = "http://localhost:5001",
                        ValidAudience = "http://localhost:5000",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("JWTDTO").GetSection("SecurityKey").Value))// "9e79234cd150108e5048d0e0cb4ca5e4"
                    };
                });
    View Code

    然后在Configure方法中增加

    app.UseAuthentication();
    app.UseAuthorization();

    在需要使用认证的服务中添加

    [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]

     JWT所使用的{发行方、使用方、key、超时时间等}放在了appsetting.json里

     Startup.cs代码参考

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using DataService01.Models;
    using DataService01.protos;
    using DataService01.Services;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.IdentityModel.Tokens;
    using System.Text;
    
    namespace DataService01
    {
        public class Startup
        {
            private readonly IConfiguration configuration;
    
            public Startup(IConfiguration configuration)
            {
                this.configuration = configuration;
            }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddGrpc();
                services.AddAuthorization(option => option.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
                {
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
                    policy.RequireClaim("sub");
                }));
                //services.AddAuthorization();
                services.AddAuthentication().AddJwtBearer(options=> {
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidIssuer = "http://localhost:5001",
                        ValidAudience = "http://localhost:5000",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("JWTDTO").GetSection("SecurityKey").Value))// "9e79234cd150108e5048d0e0cb4ca5e4"
                    };
                });
                
                services.Configure<JWTDTO>(configuration.GetSection("JWTDTO"));
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseRouting();
                app.UseHttpsRedirection();
                
                app.UseAuthentication();
                app.UseAuthorization();
    
               
    
                app.UseEndpoints(endpoints =>
                {
                    //endpoints.MapGet("/", async context =>
                    //{
                    //    await context.Response.WriteAsync("Hello World!");
                    //});
                    endpoints.MapGrpcService<ds01>();
                   // endpoints.MapGrpcService<ds02>();
                });
            }
        }
    }
    Startup.cs

    为了方便使用jwt的配置信息,创建一个JWTDTO类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace DataService01.Models
    {
        public class JWTDTO
        {
            /// <summary>
            /// 颁发者
            /// </summary>
            public string Issuer { get; set; }
            /// <summary>
            /// 使用者
            /// </summary>
            public string Audience { get; set; }
            /// <summary>
            /// 密钥key
            /// </summary>
            public string SecurityKey { get; set; }
            /// <summary>
            /// 过期时间
            /// </summary>
            public int ExpireMinutes { get; set; }
        }
    }
    JWTDTO

    在ConfigureServices 注册,在ds01.cs(服务实现类)的构造函数中注入

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using Grpc.AspNetCore.Server;
    using Grpc.AspNetCore;
    using DataService01.protos;
    using Grpc.Core;
    using System.IO;
    using Microsoft.AspNetCore.Authorization;
    using System.Configuration;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Options;
    using DataService01.Models;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    
    namespace DataService01.Services
    {
        [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
        public class ds01 : userservice.userserviceBase
        {
            private readonly ILogger<ds01> logger;
            private readonly IConfiguration configuration;
            private readonly IOptions<JWTDTO> jwt_Options;
    
            public ds01(ILogger<ds01> logger,IConfiguration configuration,IOptions<JWTDTO> Jwt_options)
            {
                this.logger = logger;
                this.configuration = configuration;
                jwt_Options = Jwt_options;
            }
            [AllowAnonymous]
            public override async Task<return_token> gettoken(get_token request, ServerCallContext context)
            {
                users _user = new users();
                _user.LoginName = request.LoginName;
                if (request.LoginName.Equals("admin") && request.Password.Equals("123456"))
                {
                   var jwttoken=await new JWTHelper().IssueJwt(_user, jwt_Options.Value);
                    return await Task.FromResult(new return_token() { Token = jwttoken.Token, ExpireTime = new DateTimeOffset(jwttoken.ExpireTime).ToUnixTimeSeconds().ToString() });
                }
                return await Task.FromResult(new return_token() { Token = "", ExpireTime = "" });
            }
            public override Task<getusersresponse> Getuser(getusers request, ServerCallContext context)
            {
                var matedata_md=context.RequestHeaders;
                foreach (var pire in matedata_md)
                {
                   logger.LogInformation($"{pire.Key}:{pire.Value}");
                    logger.LogInformation(pire.Key+":"+pire.Value);
                }
                users item = userdatas.userslist.SingleOrDefault(n => n.ID == request.ID);
                if (item != null)
                {
                    return Task.FromResult(new getusersresponse() { Code = 0, Msg = "成功", Usermodel = item });
                }
                else
                {
                    return Task.FromResult(new getusersresponse() { Code = -1, Msg = "失败" });
                }
            }
            public override async Task getall(getusers request, IServerStreamWriter<getusersresponse> responseStream, ServerCallContext context)
            {
                foreach (var item in userdatas.userslist)
                {
                    //逐步返回数据
                    await responseStream.WriteAsync(new getusersresponse()
                    {
                        Usermodel = item
                    }
                ) ;
                }
            }
            public override async Task<getusers> Add(IAsyncStreamReader<addphoto> requestStream, ServerCallContext context)
            {
                List<byte> bt = new List<byte>();
                while (await requestStream.MoveNext())//有数据进入
                {
                    bt.AddRange(requestStream.Current.Data);
                }
                //while 执行完之后表示没有数据再进来
                FileStream file = new FileStream(AppDomain.CurrentDomain.BaseDirectory+"01.png",FileMode.OpenOrCreate) ;
                file.Write(bt.ToArray(), 0, bt.Count);
                
                file.Flush();
                file.Close();
                return  new getusers() { Name = "成功",ID = 0 };
            }
            public override async Task saveall(IAsyncStreamReader<addphoto> requestStream, IServerStreamWriter<getusersresponse> responseStream, ServerCallContext context)
            {
                List<byte> bt = new List<byte>();
                while (await requestStream.MoveNext())//有数据进入
                {
                    bt.AddRange(requestStream.Current.Data);
                }
                
                //while 执行完之后表示没有数据再进来
                FileStream file = new FileStream("/01.png", FileMode.OpenOrCreate);
                file.Write(bt.ToArray(), 0, bt.Count);
    
                file.Flush();
                file.Close();
    
                //返回数据
                foreach (var item in userdatas.userslist)
                {
                    await responseStream.WriteAsync(new getusersresponse()
                    {
                        Msg = "成功",
                        Code = 0,
                        Usermodel = item
                    });
                }
            }
        }
        public class userdatas
        {
          public static IList<users> userslist = new List<users>() { 
            new users(){ID=1,Name="11",LoginName="111",Roleid=1,IsMan=true},
            new users(){ID=2,Name="22",LoginName="222",Roleid=2,IsMan=false},
            new users(){ID=3,Name="33",LoginName="333",Roleid=3,IsMan=true}
            };
        }
    }
    ds01

    在ds01 中增加一个获取jwttoken的方法,设置不鉴权

     [AllowAnonymous]
            public override async Task<return_token> gettoken(get_token request, ServerCallContext context)
            {
                users _user = new users();
                _user.LoginName = request.LoginName;
                if (request.LoginName.Equals("admin") && request.Password.Equals("123456"))
                {
                   var jwttoken=await new JWTHelper().IssueJwt(_user, jwt_Options.Value);
                    return await Task.FromResult(new return_token() { Token = jwttoken.Token, ExpireTime = new DateTimeOffset(jwttoken.ExpireTime).ToUnixTimeSeconds().ToString() });
                }
                return await Task.FromResult(new return_token() { Token = "", ExpireTime = "" });
            }

    #region 以下一段是jwt刷新的内容,我没有在代码中实现,仅参考

    由于jwt是无状态的,jwt超时后需要更新,不然刚刚还在操作的用户就被提示 认证失败了,所以需要在token过期前更新token。

    网上看到一些方案 设置两个时间,一个是token有效时间(20分钟),另一个是token存活的时间(1小时,将token存放在redis中,1小时是redis的有效时间),如果超过了token时间,就到redis中取,我个人认为不太好。

    看了下另外一个项目案例的方案觉得可以:token时间为20分钟,当请求时如果超过了时间的2/3,则返回202状态吗;客户端接收到202状态码后,客户端重新获取token,获取后再次请求业务逻辑;

    案例是vue写的,以下为服务端与客户端代码:

    function post(url, params, showLoading,config) {
      _showLoading = showLoading;
      axios.defaults.headers[_Authorization] = getToken();
      return new Promise((resolve, reject) => {
        //  axios.post(url, qs.stringify(params))   //
        axios.post(url, params,config)
          .then(response => {
            if (response.status == 202) {
              getNewToken(() => { post(url, params, _showLoading); });
              return;
            }
            resolve(response.data);
          }, err => {
            if (err.status == 202) {
              getNewToken(() => { post(url, params, _showLoading); });
              return;
            }
            reject(err.data && err.data.message ? err.data.message : '网络好像出了点问题~~');
          })
          .catch((error) => {
            reject(error)
          })
      })
    }
    
    //当前token快要过期时,用现有的token换成一个新的token
    function getNewToken(callBack) {
      ajax({
        url: "/api/User/replaceToken",
        param: {},
        json: true,
        success: function (x) {
          if (x.status) {
            let userInfo = $httpVue.$store.getters.getUserInfo();
            userInfo.token = x.data;
            currentToken = x.data;
            $httpVue.$store.commit('setUserInfo', userInfo);
            callBack();
          } else {
            console.log(x.message);
            toLogin();
          }
        },
        errror: function (ex) {
          console.log(ex);
          toLogin();
        },
        type: "post",
        async: false
      });
    
    
    }
    vue客户端

    客户端判断状态码为202时,先调用gettoken()gettoken的callback 是递归调用原方法。

                DateTime expDate = context.HttpContext.User.Claims.Where(x => x.Type == JwtRegisteredClaimNames.Exp)
                    .Select(x => x.Value).FirstOrDefault().GetTimeSpmpToDate();
                //如果过期时间小于设置定分钟数的1/3时,返回状态需要刷新token
                if (expDate < DateTime.Now || (expDate - DateTime.Now).TotalMinutes < AppSetting.ExpMinutes / 3)
                {
                    context.FilterResult(HttpStatusCode.Accepted, "Token即将过期,请更换token");//202
                    return;
                }
    服务端验证过期时间

    服务端为Webapi 在Startup.cs的 mvc增加两个过滤器,其中第一个是 用来检验jwt的。服务端返回202状态码的这段,写在过滤器里就可以。

     #endregion

    至此服务端代码结束。

    客户端

     using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001");
                var service01 = new userservice.userserviceClient(channel);
               return_token rt_token= service01.gettoken(new get_token() { LoginName = "admin", Password = "123456" });//获取JWTtoken
                var md_add_token = new Metadata() ;
                if (!string.IsNullOrEmpty(rt_token.Token))
                {
                    md_add_token.Add("Authorization", $"Bearer {rt_token.Token}");//将Token 添加到 Hearers里,key和Value 是固定写法,value中 Bearer 与token中间 要加一个空格
                }
    
                getusersresponse us = await service01.GetuserAsync(new getusers() { ID = 2 },headers:md_add_token);//一元请求+传送元数据// header是Jwttoken
                _logger.LogInformation(us.Msg);

    感谢B站Up主“软件工艺师”。
    https://gitee.com/zeran/core01.git

    仅供参考,内容中会引用部分博友的文章。(侵删)
  • 相关阅读:
    每周总结15
    第二阶段Day02
    第二阶段Day01
    假期学习记录03(高德地图错误码为7:key鉴权错误)
    假期学习记录02(高德地图关于开发版和调试版sha值)
    假期学习记录01(地图显示和基本的配置)
    android获取当前步数
    android实现轮播图(从互联网上寻找图片)
    android中gps的应用
    互联网营销精准决策项目总结
  • 原文地址:https://www.cnblogs.com/zeran/p/14481591.html
Copyright © 2011-2022 走看看