前言
最近再学习微服务,所以把自己的个人站点https://www.ttblog.site/拆分成微服务。目前正在思考微服务里面的认证与授权,网上百度到都是根据用户名和密码来实现的,考虑到实际的原因,我的个人站点是最先访问不需要登录,当执行写入或更改操作时才需要用户名和密码,所以我自己思考了一个方案,这里分享一下,设计难免有很多不合理之处,大家可以予以批评。
文档
我开始做的时候,对认证授权不是很理解,所以我在网上百度并且在博客园和开源中国提了一下问。https://www.oschina.net/question/2859520_2319077和https://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 }); }, });})
原文转载:http://www.shaoqun.com/a/479865.html
燕文物流:https://www.ikjzd.com/w/2229
lastpass:https://www.ikjzd.com/w/846
cares:https://www.ikjzd.com/w/1302
前言最近再学习微服务,所以把自己的个人站点https://www.ttblog.site/拆分成微服务。目前正在思考微服务里面的认证与授权,网上百度到都是根据用户名和密码来实现的,考虑到实际的原因,我的个人站点是最先访问不需要登录,当执行写入或更改操作时才需要用户名和密码,所以我自己思考了一个方案,这里分享一下,设计难免有很多不合理之处,大家可以予以批评。文档我开始做的时候,对认证授权不是很理解,
张洁:https://www.ikjzd.com/w/1663
亚马逊礼品卡:https://www.ikjzd.com/w/1090.html
亚马逊卖家写标题的常用几个主流套路:https://www.ikjzd.com/home/110826
大森林:https://www.ikjzd.com/w/2268
亚马逊产品质量问题频出!1400条儿童睡衣裤被紧急召回!:https://www.ikjzd.com/home/100136
No comments:
Post a Comment