学习设计微服务: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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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,对相应的路由添加节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
     "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如下:

1
2
3
4
5
6
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,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
 * 字符串转日期
 */
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
            });
        },
    });
})

  

posted @   灬丶  阅读(766)  评论(0编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示