zoukankan      html  css  js  c++  java
  • 学习设计微服务:api认证

    前言
    最近再学习微服务,所以把自己的个人站点https://www.ttblog.site/拆分成微服务。目前正在思考微服务里面的认证与授权,网上百度到都是根据用户名和密码来实现的,考虑到实际的原因,我的个人站点是最先访问不需要登录,当执行写入或更改操作时才需要用户名和密码,所以我自己思考了一个方案,这里分享一下,设计难免有很多不合理之处,大家可以予以批评。
    文档
    我开始做的时候,对认证授权不是很理解,所以我在网上百度并且在博客园和开源中国提了一下问。https://www.oschina.net/question/2859520_2319077https://q.cnblogs.com/q/129422/。并且看了很多文章,大多数使用的是IdentityServer4,但是我发现这个比较复杂,貌似还要安装一些认证,所以选择了使用JWT。并且了解了一下OAuth2,我觉得我用的应该属于里面的客户端模式https://www.jianshu.com/p/84a4b4a1e833。大概都了解之后,我就开始在项目里集成了jwt和ocelot。
    实战
    首先创建了一个认证服务器
    BlogAuthApi
    然后一个网关
    BlogGateway
    最后一个
    BlogWebApi
    我的思路就是js判断是否存有token,如果没有在请求认证服务器Auth,,返回一个token,存入浏览器,然后之后通过token去访问webapi。
    1,请求token
    我这里使用的微服务网关属于Ocelot,请求时通过网关转发到认证服务器获取token,如下代码生成token:

     public class Jwt
        {
            /// <summary>
            /// 返回jwt模型
            /// </summary>
            /// <returns></returns>
            public static JwtOption GetOption()
            {
                JwtOption option = ConfigureProvider.BuildModel<JwtOption>("jwtOption");
                return option;
            }
            /// <summary>
            ///  返回SymmetricSecurityKey
            /// </summary>
            /// <returns></returns>
            public static SymmetricSecurityKey GetSymmetricSecurityKey()
            {
                JwtOption option = GetOption();
                return GetSymmetricSecurityKey(option.Secret);
            }
            /// <summary>
            ///  返回SymmetricSecurityKey
            /// </summary>
            /// <returns></returns>
            public static SymmetricSecurityKey GetSymmetricSecurityKey(string secret)
            {
                return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            }
            /// <summary>
            /// 返回token参数模型
            /// </summary>
            /// <returns></returns>
            public static TokenValidationParameters GetTokenValidation()
            {
                JwtOption option = GetOption();
                var tokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = GetSymmetricSecurityKey(option.Secret),
                    ValidateIssuer = true,
                    ValidIssuer = option.Issuer,
                    ValidateAudience = true,
                    ValidAudience = option.Audience,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero,
                    RequireExpirationTime = true,
    
                };
                return tokenValidationParameters;
            }
            /// <summary>
            /// 获取jwt的token参数
            /// </summary>
            /// <param name="claims"></param>
            /// <returns></returns>
            public static JwtSecurityToken GetJwtParameters(Claim[] claims,JwtOption option=null)
            {
                if (option == null)
                    option = GetOption();
                var jwt = new JwtSecurityToken(
                   issuer: option.Issuer,
                   audience: option.Audience,
                   claims: claims,
                   notBefore: DateTime.Now,
                   expires: DateTime.Now.Add(TimeSpan.FromMinutes(option.ExpireMinutes)),
                   signingCredentials: new SigningCredentials(GetSymmetricSecurityKey(option.Secret), SecurityAlgorithms.HmacSha256)
                );
                return jwt;
            }
            /// <summary>
            /// 获取jwt
            /// </summary>
            /// <param name="claims"></param>
            /// <returns></returns>
            public static JwtToken GetToken(JwtSecurityToken tokenParameters)
            {
                JwtOption option = GetOption();
                string token=new JwtSecurityTokenHandler().WriteToken(tokenParameters);
                return new JwtToken(token, option.ExpireMinutes);
            }
            /// <summary>
            /// 获取jwt
            /// </summary>
            /// <param name="claims"></param>
            /// <returns></returns>
            public static JwtToken GetToken(Claim[] claims)
            {
                JwtOption option = GetOption();
                JwtSecurityToken jwtSecurityToken = GetJwtParameters(claims,option);
                string token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
                return new JwtToken(token, option.ExpireMinutes*60);
            }
        }
    
    
    
    
     public class JwtToken
        {
            public string Token { get; set; }
            public int ExpireSeconds { get; set; }
            public JwtToken(string token,int expireSeconds)
            {
                Token = token;
                ExpireSeconds = expireSeconds;
            }
        }
      public class JwtOption
        {
            public string Issuer { get; set; }
            public string Audience { get; set; }
            public int ExpireMinutes { get; set; }
            public string Secret { get; set; }
        }
    

      

     

    并且添加配置文件

     "jwtOption": {
        "issuer": "",
        "audience": "",
        "expireMinutes": "",
        "secret": ""
      }

    然后前端获取到token之后会吧token放入到header里面请求。
    2,配置网关服务Ocelot
    使用ocelot认证时,需要配置Ocelot.json,对相应的路由添加节点

     {
          "DownstreamPathTemplate": "/api/{url}",
          "DownstreamScheme": "http",
          "DownstreamHostAndPorts": [
            {
              "Host": "localhost",
              "Port": 5001
            }
          ],
          "UpstreamPathTemplate": "/{url}",
          "UpstreamHttpMethod": [ "Get", "Post", "Delete" ],
          "AuthenticationOptions": {
            "AuthenticationProviderKey": "ApiAuthKey",//认证服务的key
            "AllowedScopes": []
          },
          //限流
          "RateLimitOptions": {
            "ClientWhitelist": [],
            "EnableRateLimiting": true,
            "Period": "1s",
            "PeriodTimespan": 1,
            "Limit": 1
          }
        }
    

      

    让后需要在Startup里面添加Jwt如下:

      services.AddAuthentication()
                 .AddJwtBearer("ApiAuthKey", x =>
                 {
                     x.RequireHttpsMetadata = false;
                     x.TokenValidationParameters = tokenValidationParameters;                
                 });
    

      

    之后启动3个服务来测试下,
    当我们不传token时,请求时直接返回401的:

    然后我们请求认证服务器获取token

    然后我们把token放入header里面请求:

    可以看到请求成功了,并且我们可以看到token的过期时间为120秒,然后过了两分钟我们在请求就不行了

    到此,我的api认证功能已经大致完成了,因为自己并没有这方面的经验,例如怎么token过期了前端怎么取刷新的问题,怎么扩展ocelot过期返回的response等等,自己都是要一点一点去学习了解的,这里只是贴出我的过程,和大家分享讨论下,希望可以给出好的意见。

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    2020/09/30 16:43

    这里作为后续的更新,

    之前我已经讲过如何认证token,然后我下午又想了一个刷新token的方法,讲一下我的思路。

    首先是认证服务器返回token,过期时间,token创建时间3个字段,然后前端存储这3个字段。

    例如当一次请求的时候token为空,则请求token并存储,第二次请求时如果token有值,则根据过期时间,token创建时间来判断是否过期没如果过期了就在此请求token,并且更新当前的localStorge,代码如下

    /**
     * 字符串转日期
     */
    function stringToDate(str){
        var tempStrs = str.split(" ");
        var dateStrs = tempStrs[0].split("-");
        var year = parseInt(dateStrs[0], 10);
        var month = parseInt(dateStrs[1], 10) - 1;
        var day = parseInt(dateStrs[2], 10);
        var timeStrs = tempStrs[1].split(":");
        var hour = parseInt(timeStrs [0], 10);
        var minute = parseInt(timeStrs[1], 10);
        var second = parseInt(timeStrs[2], 10);
        var date = new Date(year, month, day, hour, minute, second);
        return date;
    }
    /**
    * 全局ajax处理
    */
    layui.use('layer', function () {
    	var layer = layui.layer;
    	$.ajaxSetup({
    		cache: false,
    		beforeSend: function (xhr) {
    			var token = localStorage.getItem('token');//token
    			var tokenExpireTime = localStorage.getItem('tokenExpireTime');//过期时间
    			var tokenSaveTime = localStorage.getItem('tokenSaveTime');//token保存时间
    			var requestToken = false;//是否需要获取token
    			if (token == undefined || tokenExpireTime == undefined || tokenSaveTime == null) {
    				requestToken = true;
    			}
    			if (!requestToken) {//不需要时判断token是否过期
    				if (tokenExpireTime == undefined) {
    					requestToken = true;
    				}
    				else {
    					var now = new Date();
    					tokenSaveTime = stringToDate(tokenSaveTime);
    					var s=now.getTime()-tokenSaveTime.getTime();
    					//计算出相差天数
    					var days=Math.floor(s/(24*3600*1000))
    					//计算出小时数
    					var leave1=s%(24*3600*1000)    //计算天数后剩余的毫秒数
    					var hours=Math.floor(leave1/(3600*1000))
    					//计算相差分钟数
    					var leave2=leave1%(3600*1000)        //计算小时数后剩余的毫秒数
    					var minutes=Math.floor(leave2/(60*1000))
    					//计算相差秒数
    					//var leave3=leave2%(60*1000)      //计算分钟数后剩余的毫秒数
    					//var seconds=Math.round(leave3/1000)
    					if(days>0)
    					{
    						requestToken = true
    					}
    					else if(hours>0){
    						requestToken = true
    					}
    					else if(minutes>tokenExpireTime){
    						requestToken = true
    					}
    				}
    			}
    			if (requestToken) {
    				$.ajax({
    					url: api + '/auth/token',
    					type: 'get',
    					datatype: 'json',
    					async:false,
    					beforeSend: function () {
    						var i=1;防止调用token时会通过ajaxStup再次执行beforeSend
    					},
    					success: function (res) {
    						if (res.code == 200) {
    							token=res.data.token;
    							localStorage.setItem('token', res.data.token);
    							localStorage.setItem('tokenExpireTime', res.data.expireMinutes);
    							localStorage.setItem('tokenSaveTime', res.data.createTime);
    						}
    					},
    					complete: function () {
    						var i=1;
    					}
    				})
    			}
    			xhr.setRequestHeader('Authorization', 'Bearer ' + token); 
    		},
    		error: function (request) {
    			layer.msg('响应服务器失败', {
    				icon: 7
    			});
    		},
    	});
    })
    

      

  • 相关阅读:
    NS2仿真:公交车移动周期模型及性能分析
    oracle 列行转换
    oracle dmp文件导出与导入
    fire workflow总结
    install mysql on centos7
    java反射机构应用
    table行颜色设置
    maven无法下依赖jar文件的解决方案
    windows环境jprofiler配置监控tomcat
    CAS tomcat6搭建
  • 原文地址:https://www.cnblogs.com/MrHanBlog/p/13753132.html
Copyright © 2011-2022 走看看